Compare commits

...
Author SHA1 Message Date
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
1231 changed files with 87515 additions and 41542 deletions
+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
+58
View File
@@ -42,3 +42,61 @@ blocking or waiting for terminal paging.
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.
+15
View File
@@ -43,6 +43,21 @@ script:
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
@@ -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
@@ -35,6 +35,10 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
([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.
@@ -158,3 +162,8 @@ operations, inspect target LLVM ADT class APIs:
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.
+9
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
@@ -182,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 -7
View File
@@ -6,13 +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.
# `unused-template`: has false positives, which we see in eval.cpp.
Suppress: [unused-function, unused-includes, unused-template]
# `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]
---
+4
View File
@@ -53,3 +53,7 @@ uv.lock
# Generated by scripts/create_compdb.py
/external
# Linux perftools output
perf.data
perf.data.old
+8
View File
@@ -20,3 +20,11 @@ contributing to the Carbon Language project.
> [!IMPORTANT] Always use `bazelisk` instead of `bazel` for all commands in the
> Carbon project. Refer to the
> [Bazel usage skill](/.agents/skills/bazel/SKILL.md) for detailed instructions.
## Version control
> [!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.
+2 -3
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-08-06.
commit = "a6b0af7536ef0ae8383ec729c5fbf23c73243776",
# HEAD as of 2026-09-08.
commit = "7024b9e1b423b3c3c6ac76ab6a73cb2c9e4ef842",
patch_cmds = ["echo \"module(name='llvm-raw')\" > MODULE.bazel"],
patch_strip = 1,
patches = [
@@ -93,7 +93,6 @@ git_override(
"//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:0006_Add_more_libc_math_excludes.patch",
"//bazel/llvm_project:0009_Introduce_starlark_exporting_compiler-rt_build_information.patch",
"//bazel/llvm_project:0011_Temporarily_remove_reference_to_hermetic_toolchain.patch",
],
remote = "https://github.com/llvm/llvm-project.git",
@@ -4,30 +4,34 @@ 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
@@ -473,5 +473,26 @@
BUILTINS_LIBC_MATH_EXCLUDES = [
# Sources requiring COMPILER_RT_USE_LIBC_MATH (e.g. LIBC_NAMESPACE::shared::*).
+ "lib/builtins/adddf3.cpp",
"lib/builtins/addtf3.cpp",
+ "lib/builtins/addsf3.cpp",
+ "lib/builtins/divdf3.cpp",
+ "lib/builtins/divsf3.cpp",
+ "lib/builtins/divtf3.cpp",
+ "lib/builtins/extenddftf2.cpp",
+ "lib/builtins/extendsfdf2.cpp",
+ "lib/builtins/extendsftf2.cpp",
+ "lib/builtins/extendxftf2.cpp",
+ "lib/builtins/muldf3.cpp",
+ "lib/builtins/mulsf3.cpp",
+ "lib/builtins/multf3.cpp",
+ "lib/builtins/negdf2.cpp",
+ "lib/builtins/negsf2.cpp",
+ "lib/builtins/subdf3.cpp",
+ "lib/builtins/subsf3.cpp",
+ "lib/builtins/subtf3.cpp",
+ "lib/builtins/truncdfsf2.cpp",
+ "lib/builtins/trunctfdf2.cpp",
+ "lib/builtins/trunctfsf2.cpp",
+ "lib/builtins/trunctfxf2.cpp",
]
@@ -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,576 +0,0 @@
From 0ee985a794692a0eba510f54657400d814f5d2fa Mon Sep 17 00:00:00 2001
From: Chandler Carruth <chandlerc@gmail.com>
Date: Wed, 24 Jun 2026 11:29:41 -0400
Subject: [PATCH] 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.
---
.../compiler-rt/BUILD.bazel | 219 +++++++++++++++---
.../compiler-rt/compiler-rt.bzl | 153 ++++++++++++
2 files changed, 336 insertions(+), 36 deletions(-)
create mode 100644 utils/bazel/llvm-project-overlay/compiler-rt/compiler-rt.bzl
index 0c5e0af4cb48..ae4a99d6885c 100644
diff --git a/utils/bazel/llvm-project-overlay/compiler-rt/BUILD.bazel b/utils/bazel/llvm-project-overlay/compiler-rt/BUILD.bazel
--- 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"],
@@ -188,9 +189,15 @@ filegroup(
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",
]
@@ -252,11 +259,11 @@ filegroup(
),
)
-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 = [
@@ -269,6 +276,55 @@ filegroup(
srcs = glob(BUILTINS_MACOS_ATOMIC_SRCS_PATTERNS),
)
+BUILTINS_LIBC_MATH_EXCLUDES = [
+ # Sources requiring COMPILER_RT_USE_LIBC_MATH (e.g. LIBC_NAMESPACE::shared::*).
+ "lib/builtins/adddf3.cpp",
+ "lib/builtins/addtf3.cpp",
+ "lib/builtins/addsf3.cpp",
+ "lib/builtins/divdf3.cpp",
+ "lib/builtins/divsf3.cpp",
+ "lib/builtins/divtf3.cpp",
+ "lib/builtins/extenddftf2.cpp",
+ "lib/builtins/extendsfdf2.cpp",
+ "lib/builtins/extendsftf2.cpp",
+ "lib/builtins/extendxftf2.cpp",
+ "lib/builtins/muldf3.cpp",
+ "lib/builtins/mulsf3.cpp",
+ "lib/builtins/multf3.cpp",
+ "lib/builtins/negdf2.cpp",
+ "lib/builtins/negsf2.cpp",
+ "lib/builtins/subdf3.cpp",
+ "lib/builtins/subsf3.cpp",
+ "lib/builtins/subtf3.cpp",
+ "lib/builtins/truncdfsf2.cpp",
+ "lib/builtins/trunctfdf2.cpp",
+ "lib/builtins/trunctfsf2.cpp",
+ "lib/builtins/trunctfxf2.cpp",
+]
+
+# 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 +
+ BUILTINS_LIBC_MATH_EXCLUDES
+ ),
+ ),
+)
+
# Apple-platform specific SME source file.
filegroup(
name = "builtins_aarch64_apple_sme_srcs",
@@ -341,11 +389,14 @@ AARCH64_OUTLINE_ATOMICS_FMT = "lib/builtins/aarch64/outline_atomic_{0}{1}_{2}.S"
# 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_aarch64_sme_os_srcs",
+ ":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
@@ -365,10 +416,20 @@ filegroup(
"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",
@@ -385,9 +446,19 @@ filegroup(
),
)
+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",
@@ -396,14 +467,52 @@ filegroup(
"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(
+ 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",
@@ -414,19 +523,66 @@ filegroup(
),
)
-# Source files for the RISC-V architecture-specific builtins.
+make_filtered_builtins_srcs_groups(
+ name = "builtins_ppc64_srcs",
+ srcs = [":builtins_unfiltered_ppc64_srcs"],
+ textual_name = "builtins_ppc64_textual_srcs",
+)
+
filegroup(
- name = "builtins_riscv_srcs",
- srcs = glob(
+ 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
# 64-bit).
filegroup(
@@ -439,8 +595,14 @@ filegroup(
# 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",
@@ -448,13 +610,29 @@ filegroup(
"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",
@@ -466,6 +644,10 @@ filegroup(
# 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",
],
),
)
@@ -473,48 +655,5 @@
-BUILTINS_LIBC_MATH_EXCLUDES = [
- # Sources requiring COMPILER_RT_USE_LIBC_MATH (e.g. LIBC_NAMESPACE::shared::*).
- "lib/builtins/adddf3.cpp",
- "lib/builtins/addtf3.cpp",
- "lib/builtins/addsf3.cpp",
- "lib/builtins/divdf3.cpp",
- "lib/builtins/divsf3.cpp",
- "lib/builtins/divtf3.cpp",
- "lib/builtins/extenddftf2.cpp",
- "lib/builtins/extendsfdf2.cpp",
- "lib/builtins/extendsftf2.cpp",
- "lib/builtins/extendxftf2.cpp",
- "lib/builtins/muldf3.cpp",
- "lib/builtins/mulsf3.cpp",
- "lib/builtins/multf3.cpp",
- "lib/builtins/negdf2.cpp",
- "lib/builtins/negsf2.cpp",
- "lib/builtins/subdf3.cpp",
- "lib/builtins/subsf3.cpp",
- "lib/builtins/subtf3.cpp",
- "lib/builtins/truncdfsf2.cpp",
- "lib/builtins/trunctfdf2.cpp",
- "lib/builtins/trunctfsf2.cpp",
- "lib/builtins/trunctfxf2.cpp",
-]
-
-# 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 +
- BUILTINS_LIBC_MATH_EXCLUDES
- ),
- ),
+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 000000000000..e33ceb6a89da
--- /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,
+ )
--
2.55.0.rc0.799.gd6f94ed593-goog
@@ -6,15 +6,15 @@ point we can remove this patch.
---
--- a/utils/bazel/llvm-project-overlay/llvm/config.bzl
+++ b/utils/bazel/llvm-project-overlay/llvm/config.bzl
@@ -48,7 +48,6 @@
@@ -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>",
@@ -56,7 +55,6 @@
@@ -80,7 +79,6 @@
})
mallinfo_defines = select({
+4 -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$$",
],
)
@@ -501,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]+)?$$",
],
)
@@ -688,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]+)?$$",
],
)
+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
+21 -18
View File
@@ -69,6 +69,14 @@ class MapView
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.
class LookupKVResult {
@@ -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
@@ -169,6 +175,8 @@ class MapBase : protected RawHashtable::BaseImpl<InputKeyT, InputValueT,
using ViewT = MapView<KeyT, ValueT, KeyContextT>;
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
@@ -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>
+58
View File
@@ -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
@@ -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
+113 -2
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,6 +40,7 @@ using RawHashtable::MoveOnlyTestData;
using RawHashtable::TestData;
using RawHashtable::TestKeyContext;
using ::testing::Pair;
using ::testing::UnorderedElementsAre;
using ::testing::UnorderedElementsAreArray;
template <typename MapT, typename MatcherRangeT>
@@ -47,9 +51,9 @@ auto ExpectMapElementsAre(MapT&& m, MatcherRangeT element_matchers) -> void {
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.
@@ -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
+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
+423 -79
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.
//
@@ -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.
+20 -21
View File
@@ -7,6 +7,7 @@
#include <concepts>
#include <type_traits>
#include <utility>
#include "common/check.h"
#include "common/hashtable_key_context.h"
@@ -60,6 +61,10 @@ class SetView : RawHashtable::ViewImpl<InputKeyT, void, InputKeyContextT> {
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.
class LookupResult {
@@ -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
@@ -131,7 +134,7 @@ 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:
@@ -143,6 +146,7 @@ class SetBase
using ViewT = SetView<KeyT, KeyContextT>;
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
@@ -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>
+70 -1
View File
@@ -35,8 +35,9 @@ 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 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); \
@@ -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
+181 -1
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,6 +24,7 @@ namespace {
using RawHashtable::IndexKeyContext;
using RawHashtable::MoveOnlyTestData;
using RawHashtable::TestData;
using ::testing::UnorderedElementsAre;
using ::testing::UnorderedElementsAreArray;
template <typename SetT, typename MatcherRangeT>
@@ -26,7 +32,9 @@ auto ExpectSetElementsAre(SetT&& s, MatcherRangeT element_matchers) -> void {
// Collect the elements into a container.
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
+25 -6
View File
@@ -1,16 +1,35 @@
// 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
package Core library "prelude/destroy";
// TODO: Add `Destructor`, as in:
// interface Destructor {
// private fn Op(ref self);
// TODO: uncomment when `require impls` adds a witness table entry.
// interface SubobjectDestroy {
// final fn Op(ref self) = "subobject.destroy";
// }
// Destroys objects. This will invoke `Destructor` impls recursively on members;
// it does not deallocate memory.
interface Destroy {
// TODO: uncomment when `require impls` adds a witness table entry.
// require impls SubobjectDestroy;
// TODO: change `Self` to `partial Self`.
fn Op(ref self);
// TODO: remove when `require impls` adds a witness table entry.
final fn SubobjectDestroy(unused ref self) {
// This function body is ignored by the toolchain. We can't use
// a built-in because the compiler treats it as an error. We
// should address this at some point, but it's not a high
// priority right now.
}
final fn SelfDestruct(ref self) {
self.Op();
self.SubobjectDestroy();
}
}
// TODO: uncomment when `require impls` adds a witness table entry.
// impl forall [T: SubobjectDestroy] partial T as Destroy {
// alias Op = T.Op;
// }
+7 -5
View File
@@ -34,12 +34,14 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
## Overview
> **TODO:** >
> **TODO:**
> [#3720: Member binding operators](/proposals/p003720-member-binding-operators.md)
> introduces an additional "member binding" step, redefines simple member access
> in terms of compound member access, and defines compound member access in
> terms of calls to user-implementable interface methods. This document must be
> updated to reflect those changes.
> and
> [#7697: Updates to member access](/proposals/p007697-updates-to-member-access.md)
> redefine simple member access in terms of compound member access, define
> instance binding in terms of calls to user-implementable interface methods,
> and add another member access form `x.impl(M)`. This document must be updated
> to reflect those changes.
A _qualified name_ is a [word](../lexical_conventions/words.md) that is preceded
by a period or a rightward arrow. The name is found within a contextually
+18
View File
@@ -333,6 +333,12 @@ jj config set --repo 'revset-aliases."trunk()"' 'trunk@upstream'
# Treat github.com/carbon-language/carbon-lang as immutable, but treat your fork
# as mutable.
jj config set --repo 'revset-aliases."immutable_heads()"' 'remote_bookmarks(*, upstream)'
# Run `prek` over the commits a push would send, and push only if they pass.
jj config set --repo aliases.push '["util", "exec", "--", "sh", "-c", "exec \"$(jj workspace root)/scripts/jj_push.sh\" \"$@\"", "jj push"]'
# Run `prek` over the changes between `trunk()` and `@`.
jj config set --repo aliases.prek '["util", "exec", "--", "sh", "-c", "exec \"$(jj workspace root)/scripts/jj_prek.sh\" \"$@\"", "jj prek"]'
```
<!-- google-doc-style-resume -->
@@ -341,6 +347,18 @@ The above assumes that you have configured the remote name `origin` to refer to
your fork and `upstream` to refer to `github.com/carbon-language/carbon-lang`,
and will need to be adjusted if you use different remote names.
The `prek` alias runs [`scripts/jj_prek.sh`](/scripts/jj_prek.sh), which runs
`prek` against `@` from anywhere in the workspace, including a non-colocated
one. Arguments go to `prek run`, so `jj prek --all-files` checks everything.
The `push` alias runs [`scripts/jj_push.sh`](/scripts/jj_push.sh), which checks
the commits the push would send and leaves anything the hooks change in a commit
for you to squash. It takes the same arguments as `jj git push`, and `--dry-run`
still runs the checks. `jj` only knows the name of an alias, not what it expands
to, so it completes file names after `jj push`.
[`scripts/completions`](/scripts/completions/README.md) has Bash, Zsh, and Fish
completions for the alias.
#### AI assistants
When using AI assistants and reviewing terminal commands, some commands which
@@ -0,0 +1,607 @@
# Updates to member access
<!--
Part of the Carbon Language project, under the Apache License v2.0 with LLVM
Exceptions. See /LICENSE for license information.
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-->
[Pull request](https://github.com/carbon-language/carbon-lang/pull/7697)
<!-- toc -->
## Table of contents
- [Abstract](#abstract)
- [Background](#background)
- [Problem](#problem)
- [Callables representing the result of `impl` lookup](#callables-representing-the-result-of-impl-lookup)
- [Accessing names with instance and non-instance overloads](#accessing-names-with-instance-and-non-instance-overloads)
- [Facets with members associated with different interfaces](#facets-with-members-associated-with-different-interfaces)
- [C++ pointer-to-member values](#c-pointer-to-member-values)
- [Calling an associated function](#calling-an-associated-function)
- [Properties](#properties)
- [Proposal](#proposal)
- [Details](#details)
- [Non-instance members](#non-instance-members)
- [Callables for member functions](#callables-for-member-functions)
- [Overloading](#overloading)
- [Explicit about instance binding](#explicit-about-instance-binding)
- [C++ pointer-to-member values](#c-pointer-to-member-values-1)
- [`typeof`](#typeof)
- [Associated function example](#associated-function-example)
- [Rationale](#rationale)
- [Alternatives considered](#alternatives-considered)
- [Different way to distinguish whether instance binding occurs](#different-way-to-distinguish-whether-instance-binding-occurs)
- [Non-instance members could implement the binding interfaces](#non-instance-members-could-implement-the-binding-interfaces)
- [Other member access operators](#other-member-access-operators)
- [Bind interfaces only used for compound member access](#bind-interfaces-only-used-for-compound-member-access)
<!-- tocstop -->
## Abstract
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.
Associated functions of interfaces are also made callable when the `Self` type
can be deduced from the arguments. This also performs `impl` lookup.
## Background
- The
["qualified names and member access" design document](/docs/design/expressions/member_access.md)
reflects the design up to and including:
- [Proposal #989: Member access expressions](https://github.com/carbon-language/carbon-lang/pull/989)
- [Proposal #2360: Types are values of type `type`](https://github.com/carbon-language/carbon-lang/pull/2360)
- [Proposal #3646: Tuples and tuple indexing](https://github.com/carbon-language/carbon-lang/pull/3646)
- [Proposal #3720: Member binding operators](https://github.com/carbon-language/carbon-lang/pull/3720)
updated the member access rules to add customization of how member access
worked by implementing binding interfaces. It defined some simple member
accesses as rewrites into compound member access.
- [Pull request #7557](https://github.com/carbon-language/carbon-lang/pull/7557)
attempted to update the
[member access design doc](/docs/design/expressions/member_access.md) to
reflect the changes in
[Proposal #3720](https://github.com/carbon-language/carbon-lang/pull/3720).
- [Leads issue #7606: Should associated function names be callable?](https://github.com/carbon-language/carbon-lang/issues/7606)
added another construct that performs `impl` lookup.
- There were several discussions to figure out how to resolve the ambiguous
points and resolve the problems we discovered:
- [#generics-and-templates discussion on 2026-08-20 on Discord](https://discord.com/channels/655572317891461132/941071822756143115/1540063686201311342)
- [discussion on 2026-08-24](https://docs.google.com/document/d/1mjllGO3ZCL4qGt9uJHUtcxKoHAGEY7Y999ie4EtBWB8/edit?tab=t.3ot8c9eu3e1h#heading=h.bpukwg8e3446)
- [#typesystem discussion starting 2026-08-27 on Discord](https://discord.com/channels/655572317891461132/708431657849585705/1542650207525929070)
- [discussion on 2026-08-28](https://docs.google.com/document/d/1mjllGO3ZCL4qGt9uJHUtcxKoHAGEY7Y999ie4EtBWB8/edit?pli=1&tab=t.3ot8c9eu3e1h#heading=h.s780u75i71d1)
- [discussion on 2026-08-31](https://docs.google.com/document/d/1mjllGO3ZCL4qGt9uJHUtcxKoHAGEY7Y999ie4EtBWB8/edit?pli=1&tab=t.3ot8c9eu3e1h#heading=h.4ij84uxqftu5)
- [discussion on 2026-09-10](https://docs.google.com/document/d/1mjllGO3ZCL4qGt9uJHUtcxKoHAGEY7Y999ie4EtBWB8/edit?tab=t.8973cke7ijhm#heading=h.gxlscluaucu0)
## Problem
[Proposal #3720: Member binding operators](https://github.com/carbon-language/carbon-lang/pull/3720)
introduced some problems discovered when trying to update the design documents
in [PR #7557](https://github.com/carbon-language/carbon-lang/pull/7557):
- No clear story for what use cases are solved by `BindToType` and when that
customization would be needed.
- Simple member access `a.b` was defined as a rewrite to compound member
access `a.(M)`, but the definition of compound member access depended on
whether `M` was an instance member, and so would not always have the desired
behavior.
- Unclear story around how we support overloading between instance and
non-instance members. It seem to involve a default implementation of the
binding interfaces for all types for use by non-instance members. Could
non-instance members be repeatedly bound?
We thought it should be more obvious in which cases instance binding would
occur. In this example,
```
class C {
extend base: (i32, i32);
static let template n: i32 = 0;
}
var x: C = {.base = (1, 2)};
```
Does `x.n` have the value `0` like `C.n` or `1` like `x.(0)`? The interpretation
depends on whether the binding interfaces are implemented for these types. In a
generic context, that could be unknown at checking time, as in this example:
```carbon
class D(T: Core.Default) {
static let template x: T = T.Op();
}
fn F[T: type](d: D(T)) {
// Does the result here have value `T`, or is it the
// result of binding `T.Op()` to `d`?
d.x
}
```
This would lead to awkward constraints on types in order to get expected normal
behavior in generic code.
The implementation of #3720 in the toolchain also looked to be expensive, with
broad blanket implementations of interfaces to get the expected default
behavior, leading to lots of `impl` lookups. As much as possible, builtin
`impl`s should be `final` or narrow.
### Callables representing the result of `impl` lookup
We want some way of creating callables from methods and member function from
interfaces, with the option of binding or not binding `self` for associated
methods.
```carbon
interface I {
fn F();
fn M(self);
}
class C {}
impl C as I { ... }
fn G(c: C) {
// Would like to make callables for:
// - impl lookup of `I.F` for `C`
// - impl lookup of `I.M` for `C` taking a `C` parameter for `self`
// - impl lookup of `I.M` for `C` where the `self` parameter is bound to `c`.
}
```
Before this proposal, the behavior of compound member access was different for
instance methods and non-instance member functions associated with an interface:
- `C.(I.F)` would produce the result of looking up `I.F` for `C`.
- `c.(I.F)` was invalid, since `c` is not a type, and so can't implement `I`.
- `C.(I.M)` was invalid, since `I.M` is an instance member and can't perform
instance binding to `C`.
- `c.(I.M)` would perform `impl` lookup and then instance binding of `I.M` to
`c`.
And there was no way, beyond writing a lambda, to get the result of `impl`
lookup of `I.M` in `C` without performing instance binding.
### Accessing names with instance and non-instance overloads
[Proposal #3720: Member binding operators](https://github.com/carbon-language/carbon-lang/pull/3720)
erased some of the differences between instance and non-instance members in
order to support names that had both as overloads, to pave the way for
overloading to be added to the language, as in (using the function overload
syntax from
[discussion on 2025-03-28](https://docs.google.com/document/d/1Iut5f2TQBrtBNIduF4vJYOKfw7MbS8xH_J01_Q4e6Rk/edit?resourcekey=0-mc_vh5UzrzXfU4kO-3tOjA&tab=t.0#heading=h.t6l733mu79i7))
```carbon
interface I {
overload F {
fn (self) -> f32;
fn (i32) -> bool;
}
}
class C {
overload G {
fn (self) -> f32;
fn (i32) -> bool;
}
}
```
However, not all of the differences were eliminated, so it was unclear whether
the rewrite from simple to compound member access should use the type or value
of the left operand.
### Facets with members associated with different interfaces
We would like to support members of facets that are associated entities of
different interfaces, as in this example:
```carbon
interface I {
fn F();
fn M(self);
}
interface J {
require impls I;
alias I_F = I.F;
alias I_M = I.M;
}
fn G[T: J](x: T) {
// `T` is a facet of `J`, but the names `I_F` and `I_M`
// from `J` refer to members of `I`. Access to those
// members by way of `x` or `T` should work and use the
// implementation of `I` by `T`.
}
```
This means member access into facets still needs to perform `impl` lookup, even
though in many cases the facet has the implementation in its witness.
### C++ pointer-to-member values
[C++ pointer-to-member](https://en.cppreference.com/cpp/language/pointer)
values should be usable from Carbon once bound to an instance.
```carbon
import Cpp inline '''
struct A {
int m;
auto F() -> int;
};
int A::* p = &A::m;
int (A::* q)() = &A::F;
''';
fn G(ref a: Cpp.A) -> i32 {
// Equivalent to `a.*p + (a.*q)()` in C++.
// Evaluates to `a.m + a.F()`.
return a.(Cpp.p) + a.(Cpp.q)();
}
```
### Calling an associated function
Following
[Leads issue #7606: Should associated function names be callable?](https://github.com/carbon-language/carbon-lang/issues/7606),
associated method of an interface should be allowed, with the `self` parameter
and `Self` type determined by the first argument, similar to how
[proposal #7016](p007016-updating-self-syntax-and-adding-static-member-variables.md#calling-without-method-syntax)
added support for calling methods as ordinary functions with the `self` passed
as the first explicit argument in the explicit parameter list `(`...`)`:
```carbon
interface Interface {
fn Method(self);
}
class Class {
extend impl as Interface { fn Method(unused self) {} }
}
fn Fn(value: Class) {
// Allowed as of proposal #7016:
(Class as Interface).Method(value);
// Leads decided should be allowed in #7606:
Interface.Method(value);
}
```
The specifics of the decision in #7606 allow non-method associated functions of
an interface, as long as the `Self` parameter may be deduced from its arguments.
This is desirable since otherwise this case would not have an ergonomic syntax.
### Properties
See the
[future work section on properties in proposal #3720](/proposals/p003720-member-binding-operators.md#future-properties).
For purposes of this proposal, we want it clear that the custom code for
producing values for a property would be invoked by instance binding, and so it
is important that instance binding happen when writing expressions that looked
like ordinary member access. Ideally we would also have another syntax available
to be able to talk about the property before instance binding.
## Proposal
We provisionally define `typeof(x)` to give the static type of the expression
`x` without any runtime evaluation of `x`.
Compound member access `a.(m)` performs two steps:
- If `m` is an associated entity, perform `impl` lookup with the `Self` type
set to the type of `a`. This must succeed and be valid.
- For an interface `I` and class `C`, both `C.(I.F)` and `I.(I.F)` will
fail due to `typeof(a) == type` not implementing `I`.
- Instance binding is performed, using either the `BindToValue` or `BindToRef`
interfaces implemented by `typeof(a)`. As in
[proposal #3720](https://github.com/carbon-language/carbon-lang/pull/3720),
the compiler provides `final` builtin implementations to provide the
previous instance binding behavior.
Simple member access `a.b` depends on what kind of entity `a` is:
- If `a` is a namespace or package, only name lookup for `b` is performed.
- If `a` names a non-type facet, then `b` is looked up in the type of `a`
(which by definition is a facet type such as an interface). If the lookup
finds an associated entity, then `impl` lookup is performed. This lookup
commonly can be satisfied by the facet `a`. This `impl` lookup is needed to
address the
["facets with members associated with different interfaces" problem](#facets-with-members-associated-with-different-interfaces).
- If `a` names a facet type, then `a.b` performs name lookup for `b` in `a`.
- If `a` names another type (including any class), then `a.b` performs name
lookup for `b` in `a`. If the result of lookup is an associated entity, then
`impl` lookup is performed.
- Otherwise, `a.b` is performed in two steps:
- `m` is set to the result of evaluating `typeof(a).b`;
- Instance binding is performed to bind `a` to `m`. This is done
unconditionally, unlike prior to this proposal.
In this last case:
- `typeof(a)` will always be a facet type or other type, so `typeof(a).b`
will always be resolved using one of the above rules, and won't require
further rewrites.
- If `b` is an associated entity, `typeof(a).b` will perform `impl` lookup
using `typeof(a)`.
- As long as the result `m` is not itself an associated entity, `a.b`
will be equivalent to `a.(m)`. We don't say that `a.b` is rewritten to
`a.(typeof(a).b)` because we want a member access to perform at most
one `impl` lookup.
Instead of writing `C.(I.F)` to perform `impl` lookup, we introduce new syntax
`C.impl(I.F)`. This always performs `impl` lookup with the the `Self` type equal
to `C` and nothing else. It is invalid unless `C` is known to implement `I`
(this check is delayed until the expression is no longer template dependent).
As a result, the left argument must always be a type or facet.
In addition, `impl` lookup occurs when
[calling an associated function of an interface](#calling-an-associated-function).
An associated function of an interface `I` is callable, and in a call to it, the
`Self` parameter is treated as a generic parameter that can be deduced. After
`Self` is deduced, `impl` lookup is performed for `Self as I`, and the
corresponding function from the impl is called. Note that this is allowed for
any associated function for which `Self` can be deduced, not just for associated
methods.
## Details
Simple member access `a.b` requires knowing what kind of entity the first `a`
operand is. In generic code, it might not be known whether a symbolic value
represents a type or some other kind of value. In that case, though, simple
member access isn't useful since we don't know enough about `a` to perform name
lookup into it.
### Non-instance members
Non-instance members of types (including classes and interfaces) no longer
implement the binding interfaces, and so may not be used with instance binding.
```carbon
interface I {
// Non-instance member function
fn F();
}
class C {
// Non-instance member function
fn G();
// Non-instance static data member
static var s: i32;
extend impl as I;
}
fn PreviouslyAllowedNowInvalid(x: C) {
// Previously allowed, but now invalid:
// ❌ x.F();
// ❌ x.G();
// ❌ x.s = 1;
}
fn Instead(x: C) {
// Instead, these should be written:
C.F(); // ✅
C.impl(I.F)(); // ✅
typeof(x).F(); // ✅
C.G(); // ✅
typeof(x).G(); // ✅
C.s = 1; // ✅
typeof(x).s = 1; // ✅
}
```
We require that the caller distinguish whether they are performing instance
binding, which means that changing a method to a non-instance member function
requires updating callers.
### Callables for member functions
Thanks to the new `a.impl(m)` syntax, we can now produce callables for all of
the cases in the
["callables representing the result of `impl` lookup" section](#callables-representing-the-result-of-impl-lookup):
```carbon
interface I {
fn F();
fn M(self);
}
class C {}
impl C as I { ... }
fn G(c: C) {
// impl lookup of `I.F` for `C`: `C.impl(I.F)`
C.impl(I.F)();
// or:
typeof(c).impl(I.F)();
// impl lookup of `I.M` for `C` taking a `C` parameter for `self`: `C.impl(I.M)`.
// This may be called with `c` passed in for `self` using:
C.impl(I.M)(c);
// or:
c.(C.impl(I.M))();
// impl lookup of `I.M` for `C` where the `self` parameter is bound to `c`:
// `c.(I.M)`
c.(I.M)();
// Equivalent to `c.(I.M)()`:
I.M(c);
}
```
Note how this changes the meaning of compound member access from before this
proposal:
- `C.(I.F)` used to produce the result of looking up `I.F` for `C`, but is no
longer valid since instance binding to `C` fails. The new syntax
`C.impl(I.F)` is used instead.
- `c.(I.F)` and `C.(I.M)` remain invalid.
- The common case of `c.(I.M)` retains its previous meaning.
### Overloading
The
[overloading problem](#accessing-names-with-instance-and-non-instance-overloads)
is addressed by not using anything about the second operand to decide whether to
perform instance binding or whether to use the value or type of the left operand
for `impl` lookup. The only fact about the right operand that is used in the
proposed rules is whether it names an associated entity.
### Explicit about instance binding
With this proposal, `a.(m)` always performs instance binding, and `a.b` performs
instance binding unless `a` is a kind of entity where we never perform instance
binding such as packages and namespaces. Cases where you want to avoid instance
binding now have a separate syntax (`typeof(a).impl(m)`).
This means that generic code has a clear meaning, and transforming non-generic
code to be generic won't change behavior.
For [properties](#properties), this means that the normal ways of accessing
members will perform the instance binding that triggers the evaluation of the
property, but there is an opt-out syntax (`typeof(a).m`) when that is not
desired.
### C++ pointer-to-member values
The solution to
[the "C++ pointer-to-member values" problem](#c-pointer-to-member-values) from
[proposal #3720](https://github.com/carbon-language/carbon-lang/pull/3720)
continues to work.
### `typeof`
`typeof(x)` has no runtime side effects, and produces a compile-time result.
This may involve compile-time evaluation, but all runtime effects from that
evaluation are discarded before code generation, as if the code is in an
`if (false)` block. For example:
```carbon
musteval fn P(T: type) -> type {
return T*;
}
fn F[template T: type](ref x: T) -> P(T) {
x += 1;
return &x;
}
fn Call() {
var y: i32 = 0;
// Involves the compile-time evaluation of `P(i32)`,
// and forming a specific instance of `F`. However,
// `F(ref y)` is not called at runtime.
StaticAssert(typeof(F(ref y)) == i32*);
Assert(y == 0);
}
```
### Associated function example
Here is an example from
[leads issue #7606](https://github.com/carbon-language/carbon-lang/issues/7606)
where we expect `Self` to be deduced and used for `impl` lookup when calling a
non-method associated function:
```carbon
interface Printable {
// Print one `Self` object.
fn Print(self);
// Print a sequence of `Self` objects.
fn PrintSlice(s: slice(Self));
}
impl Widget as Printable { ... }
fn PrintWidgets(s: slice(Widget)) {
// OK, deduces `Self` is `Widget`. Equivalent to
// `(Widget as Printable).PrintSlice(s)`.
Printable.PrintSlice(s);
}
```
Note that `Self` is in deducible position, but not directly the type of any
argument.
## Rationale
This proposal makes the behavior of member access more explicit, reducing
context sensitivity in accordance with
[the Carbon principle](/docs/project/principles/low_context_sensitivity.md).
Reducing ambiguity improves
[code readability](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write),
as does eliminating needing
[extra constraints for generic code](#explicit-about-instance-binding).
## Alternatives considered
### Different way to distinguish whether instance binding occurs
Instead of using `a.impl(b)` to skip instance binding, other syntax ideas were
[considered on 2026-08-28](https://docs.google.com/document/d/1mjllGO3ZCL4qGt9uJHUtcxKoHAGEY7Y999ie4EtBWB8/edit?tab=t.3ot8c9eu3e1h#heading=h.s780u75i71d1)
- `a.(b)` would not do instance binding, and `a.[b]` would. This made the more
common case of instance binding look more unusual, and didn't provide a
keyword in the rarer case that could be used to search the documentation or
the web to understand what it meant.
- `a.static(b)` was considered instead of `a.impl(b)`. `impl` was preferred
since it better conveyed that `impl` lookup is the only thing that happens
in that operation.
### Non-instance members could implement the binding interfaces
In
[discussion on 2026-09-10](https://docs.google.com/document/d/1mjllGO3ZCL4qGt9uJHUtcxKoHAGEY7Y999ie4EtBWB8/edit?tab=t.8973cke7ijhm#heading=h.gxlscluaucu0),
we considered letting [non-instance members](#non-instance-members) implement
the binding interfaces. There were a few variations:
- Instance binding to a non-instance member could do nothing. This would allow
repeated bindings, which was undesirable on its own, since none of them
would be doing anything, obscuring the meaning of the code. This was also
inconsistent with instance members where repeated binding is forbidden.
- Instance binding could do nothing except change the type to something that
wasn't bindable again, but otherwise operated similarly. This would have to
be restricted to non-instance member functions, so that we could forward
just the call operator, not an unbounded set of operations. This is
something we would consider in the future to match C++ and allow evolution
of methods into non-instance functions, but creates additional complexity
and inconsistency with other non-instance members like static data members.
Since the main point of customization for types is whether they implement the
binding interfaces, we have less context than C++ about what syntax was used to
arrive at an instance binding. It wasn't clear how to make a rule that allowed
non-instance accesses like C++ without allowing code we wanted to forbid like
`i32.(bool.(5))`.
### Other member access operators
We considered introducing a `::` that primarily did qualified name lookup. This
didn't address the root causes of the problem, though, which was the varying
behavior after name lookup completed.
### Bind interfaces only used for compound member access
The bind interfaces needed to be used with simple member access as well,
otherwise properties would appear different than other members.
+57
View File
@@ -0,0 +1,57 @@
<!--
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
-->
# Shell completions for `jj push`
Completions for a `jj push` alias that runs
[`scripts/jj_push.sh`](/scripts/jj_push.sh). See
[the Jujutsu section of the contribution tools doc](/docs/project/contribution_tools.md#jujutsu-jj)
to set up the alias.
`jj` only knows the name of an alias, not what it expands to, so it completes
file names after `jj push`. Each file here rewrites `push` to `git push` in the
command line before passing it to `jj`, giving the alias the flags, bookmarks,
revsets, and remotes of `jj git push`.
Each file loads `jj`'s own completions itself, and needs `jj` on `PATH` when it
runs. Remove any other setup that loads `jj`'s completions.
Run the commands below from your Carbon checkout, so that
`jj workspace root` fills in its path.
## Bash
```sh
echo "source $(jj workspace root)/scripts/completions/jj_push.bash" >>~/.bashrc
```
If your distribution ships a `jj` file in
`/usr/share/bash-completion/completions`, Bash loads it on demand and it
overrides this. Symlink this file to
`~/.local/share/bash-completion/completions/jj` instead of sourcing it.
## Zsh
```sh
echo "source $(jj workspace root)/scripts/completions/jj_push.zsh" >>~/.zshrc
```
This has to come after `compinit` in `.zshrc`, so move the line if `compinit`
runs later in the file.
## Fish
Fish loads completions on demand, after running `config.fish`, so sourcing this
at startup doesn't work: what fish loads later is added on top. Install it as
the file fish loads for `jj`:
```sh
ln -s "$(jj workspace root)/scripts/completions/jj_push.fish" \
~/.config/fish/completions/jj.fish
```
`~/.config/fish/completions` is first in `$fish_complete_path`, so this
overrides any `jj.fish` from your distribution.
+41
View File
@@ -0,0 +1,41 @@
# 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
#
# Bash completions for a `jj push` alias that runs `scripts/jj_push.sh`. See
# `scripts/completions/README.md` for how to install this.
#
# `jj` only knows the name of an alias, not what it expands to, so it completes
# file names after `jj push`. Rewriting `push` to `git push` in the command line
# before passing it to `jj` gets the completions of `jj git push`.
source <(COMPLETE=bash jj)
_carbon_jj_complete() {
# Shadow the two variables `jj`'s completion function reads. Bash scopes them
# dynamically, so it sees the rewrite below.
local -a COMP_WORDS=("${COMP_WORDS[@]}")
local COMP_CWORD=$COMP_CWORD
local i
# Only look before the cursor. A `push` at the cursor is still being typed.
for ((i = 1; i < COMP_CWORD; i++)); do
case "${COMP_WORDS[i]}" in
-*) ;;
push)
COMP_WORDS=("${COMP_WORDS[@]:0:i}" git push "${COMP_WORDS[@]:i+1}")
((COMP_CWORD++))
break
;;
*) break ;;
esac
done
_clap_complete_jj "$@"
}
if [[ "${BASH_VERSINFO[0]}" -eq 4 && "${BASH_VERSINFO[1]}" -ge 4 || "${BASH_VERSINFO[0]}" -gt 4 ]]; then
complete -o nospace -o bashdefault -o nosort -F _carbon_jj_complete jj
else
complete -o nospace -o bashdefault -F _carbon_jj_complete jj
fi
+37
View File
@@ -0,0 +1,37 @@
# 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
#
# Fish completions for a `jj push` alias that runs `scripts/jj_push.sh`. See
# `scripts/completions/README.md` for how to install this.
#
# `jj` only knows the name of an alias, not what it expands to, so it completes
# file names after `jj push`. Rewriting `push` to `git push` in the command line
# before passing it to `jj` gets the completions of `jj git push`.
#
# This replaces the completions fish loads for `jj`, so it has to be installed
# with that file's name. Loading both leaves `jj`'s file-name completion
# registered.
function __jj_completion_tokens --description 'Command line tokens, with the `push` alias expanded'
# `--cut-at-cursor` drops the token being completed, so any `push` here is
# a complete word.
set -l tokens (commandline --current-process --tokenize --cut-at-cursor)
for i in (seq 2 (count $tokens))
switch $tokens[$i]
case '-*'
continue
case push
printf '%s\n' $tokens[1..(math $i - 1)] git push \
$tokens[(math $i + 1)..-1]
return
case '*'
break
end
end
printf '%s\n' $tokens
end
complete -e -c jj
complete --keep-order --exclusive --command jj \
--arguments "(COMPLETE=fish jj -- (__jj_completion_tokens) (commandline --current-token))"
+33
View File
@@ -0,0 +1,33 @@
# 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
#
# Zsh completions for a `jj push` alias that runs `scripts/jj_push.sh`. See
# `scripts/completions/README.md` for how to install this.
#
# `jj` only knows the name of an alias, not what it expands to, so it completes
# file names after `jj push`. Rewriting `push` to `git push` in the command line
# before passing it to `jj` gets the completions of `jj git push`.
source <(COMPLETE=zsh jj)
_carbon_jj_complete() {
local i
# Only look before the cursor. A `push` at the cursor is still being typed.
for ((i = 2; i < CURRENT; i++)); do
case ${words[i]} in
-*) ;;
push)
words=(${words[1, i - 1]} git push ${words[i + 1, -1]})
((CURRENT++))
break
;;
*) break ;;
esac
done
_clap_dynamic_completer_jj "$@"
}
compdef _carbon_jj_complete jj
+1 -4
View File
@@ -196,10 +196,7 @@ def main() -> None:
bazel, args.alsologtostderr, args.dump_files, args.extra_bazel_flag
)
print(
"Generating compile_commands.json (may take a few minutes)...",
flush=True,
)
print("Generating compile_commands.json...", flush=True)
subprocess.run(
[
bazel,
+13 -2
View File
@@ -15,6 +15,12 @@ set -eu
# both check the wrong thing and fail to write back their fixes.
HEAD="$(jj show --no-patch -r @ --template 'commit_id')"
# Run from the workspace root. Setting `GIT_DIR` makes git treat the current
# directory as the work tree, and prek looks there for its configuration, so
# running from a subdirectory would find neither. Hooks also expect paths
# relative to the root.
cd "$(jj workspace root --ignore-working-copy)"
# Find the .git directory. The working copy was snapshotted above, so this
# doesn't need to do so again.
export GIT_DIR="$(jj git root --ignore-working-copy)"
@@ -24,5 +30,10 @@ export GIT_INDEX_FILE="$(mktemp)"
trap 'rm -f "$GIT_INDEX_FILE"' EXIT
git read-tree "$HEAD"
# Run prek with the `.git` directory and index we built earlier.
prek run --from-ref trunk --to-ref "$HEAD"
# Run prek with the `.git` directory and index we built earlier. Arguments
# select what gets checked; with none, check everything between `trunk` and `@`.
if (($# > 0)); then
prek run "$@"
else
prek run --from-ref trunk --to-ref "$HEAD"
fi
+112
View File
@@ -0,0 +1,112 @@
#!/usr/bin/env bash
#
# 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
#
# Runs prek over the commits `jj git push` would send, and pushes only if they
# pass. Takes the same arguments as `jj git push`.
set -eu
JJ_PREK="$(dirname "${BASH_SOURCE[0]}")/jj_prek.sh"
# Succeeds if the working-copy commit is in the given revset. This snapshots the
# working copy, so it sees anything the hooks rewrote.
working_copy_is() {
[[ -n "$(jj log --no-graph -r "@ & ($1)" --template 'commit_id')" ]]
}
# Succeeds if the working copy is an empty, undescribed child of the target,
# which hooks can run in and write their fixes into.
working_copy_sits_on() {
working_copy_is "empty() & description(exact:\"\") & children($1)"
}
# Returns the working copy to where it started. jj discards an empty,
# undescribed commit when the working copy moves off it, so the original may be
# gone. Build a new one on the same parents in that case.
restore_working_copy() {
if [[ -n "$(jj log --no-graph --ignore-working-copy \
-r "present($ORIG_CHANGE)" --template 'commit_id')" ]]; then
jj edit --quiet "$ORIG_CHANGE"
else
jj new --quiet $ORIG_PARENTS
fi
}
# `--help` describes `jj git push`, and has nothing to check.
for arg in "$@"; do
case "$arg" in
-h | --help)
exec jj git push "$@"
;;
esac
done
# Ask jj what the push would do. This checks the arguments and lists the commits
# being sent, without contacting the remote. A `--dry-run` already in `$@` is
# harmless here, and still suppresses the push at the end.
if ! PLAN="$(jj git push --dry-run "$@" 2>&1)"; then
echo "$PLAN" >&2
exit 1
fi
REMOTE="$(sed -n 's/^Changes to push to \(.*\):$/\1/p' <<<"$PLAN")"
# Each updated bookmark or tag reports the commit it moves to. Deletions have no
# such commit, and send nothing to check.
TARGETS="$(sed -E -n 's/^ (bookmark|tag): .* to ([0-9a-f]{8,})\]$/\2/p' <<<"$PLAN" |
paste -sd '|' -)"
# Nothing to check, so just push.
if [[ -z "$REMOTE" || -z "$TARGETS" ]]; then
exec jj git push "$@"
fi
# Only the heads need checking; a head's range covers everything below it.
HEADS="$(jj log --no-graph --ignore-working-copy -r "heads($TARGETS)" \
--template 'commit_id ++ "\n"')"
ORIG_CHANGE="$(jj log --no-graph -r @ --template 'change_id')"
ORIG_PARENTS="$(jj log --no-graph -r 'parents(@)' --template 'commit_id ++ " "')"
for target in $HEADS; do
# Check with the working copy on top of the target.
made_scratch=0
if ! working_copy_sits_on "$target"; then
jj new --quiet "$target"
made_scratch=1
fi
# Check from the newest ancestor already on the remote. When there is none,
# there is no range to diff, so check every file.
base="$(jj log --no-graph --ignore-working-copy \
-r "heads(::$target & ::remote_bookmarks(remote=exact:$REMOTE))" \
--template 'commit_id ++ "\n"' | head -n 1)"
if [[ -n "$base" ]]; then
check=(--from-ref "$base" --to-ref "$target")
else
check=(--all-files)
fi
result=0
"$JJ_PREK" "${check[@]}" || result=$?
# Discard the scratch commit unless the hooks wrote something into it.
if ((made_scratch)) && working_copy_is 'empty()'; then
restore_working_copy
fi
if ((result)); then
echo >&2
if ! working_copy_is 'empty()'; then
change="$(jj log --no-graph -r @ --template 'change_id.shortest()')"
echo "Hooks changed files. They are in $change." >&2
fi
echo "Error: checks failed, nothing pushed." >&2
exit 1
fi
done
exec jj git push "$@"
+7 -5
View File
@@ -85,24 +85,26 @@ Example usage:
"class": "SemIR::MakeClassId",
"constant": "SemIR::MakeConstantId",
"constraint": "SemIR::MakeNamedConstraintId",
"symbolic_constant": "SemIR::MakeSymbolicConstantId",
"entity_name": "SemIR::MakeEntityNameId",
"declared_facet_type": "SemIR::MakeDeclaredFacetTypeId",
"default_value": "SemIR::MakeDefaultValueId",
"entity_name": "SemIR::MakeEntityNameId",
"function": "SemIR::MakeFunctionId",
"generated_function": "SemIR::MakeGeneratedFunctionId",
"generic": "SemIR::MakeGenericId",
"identified_facet_type": "SemIR::MakeIdentifiedFacetTypeId",
"impl": "SemIR::MakeImplId",
"inst_block": "SemIR::MakeInstBlockId",
"inst": "SemIR::MakeInstId",
"inst_block": "SemIR::MakeInstBlockId",
"interface": "SemIR::MakeInterfaceId",
"import_ir_inst": "SemIR::MakeImportIRInstId",
"name": "SemIR::MakeNameId",
"name_scope": "SemIR::MakeNameScopeId",
"identified_facet_type": "SemIR::MakeIdentifiedFacetTypeId",
"require_block": "SemIR::MakeRequireImplsBlockId",
"require": "SemIR::MakeRequireImplsId",
"require_block": "SemIR::MakeRequireImplsBlockId",
"specific": "SemIR::MakeSpecificId",
"specific_interface": "SemIR::MakeSpecificInterfaceId",
"struct_type_fields": "SemIR::MakeStructTypeFieldsId",
"symbolic_constant": "SemIR::MakeSymbolicConstantId",
"type": "SemIR::MakeTypeId",
}
+7
View File
@@ -284,6 +284,13 @@ Supported comment markers are:
Output line matchers may contain `[[@LINE+offset]` and `{{regex}}` syntaxes,
similar to `FileCheck`.
When the file uses an `AUTOUPDATE-SPLIT`, only `CHECK` lines in that split
are matchers; elsewhere they are ordinary content. This is what allows a
split to hold a test file that itself contains `CHECK` lines, as the
language server's SemIR tests do. Note that such a split still can't contain
a `// ---` line, which would split the enclosing file; write the `/`
characters as `[[@0x2f]]` to avoid that.
- ```
// TIP: <tip>
```
+33 -9
View File
@@ -25,6 +25,9 @@ using ::testing::Matcher;
using ::testing::MatchesRegex;
using ::testing::StrEq;
// The name of the trailing split that autoupdate writes `CHECK` lines into.
static constexpr llvm::StringLiteral AutoupdateSplit = "AUTOUPDATE-SPLIT";
// Represents the different kinds of version-control conflict markers that are
// relevant for the autoupdater. One key concern here is the distinction between
// "snapshot" and "diff" conflict regions. Snapshot regions are the more
@@ -735,6 +738,22 @@ static auto TryConsumeSetFlag(llvm::StringRef line_trimmed,
return true;
}
// Returns whether the file uses a `// --- AUTOUPDATE-SPLIT` split.
//
// Autoupdate writes every `CHECK` into that split, so when one is present, a
// `CHECK` line anywhere else is part of the test input rather than an
// expectation. That's what allows a split to hold a test file that itself
// contains `CHECK` lines.
static auto UsesAutoupdateSplit(llvm::StringRef content) -> bool {
for (llvm::StringRef line : llvm::split(content, '\n')) {
llvm::StringRef trimmed = line.trim();
if (trimmed.consume_front("// ---") && trimmed.trim() == AutoupdateSplit) {
return true;
}
}
return false;
}
// Process content for either the main file (with `test_file` and
// `found_autoupdate` provided) or an included file (with those arguments null).
//
@@ -761,6 +780,8 @@ static auto ProcessFileContent(llvm::StringRef filename,
// Otherwise conflict markers are errors.
auto previous_conflict_marker = MarkerKind::None;
const bool uses_autoupdate_split = UsesAutoupdateSplit(content_cursor);
SplitState split_state;
while (!content_cursor.empty()) {
@@ -802,13 +823,18 @@ static auto ProcessFileContent(llvm::StringRef filename,
continue;
}
CARBON_ASSIGN_OR_RETURN(
is_consumed,
TryConsumeCheck(running_autoupdate, line_index, line, line_trimmed,
test_file ? &test_file->expected_stdout : nullptr,
test_file ? &test_file->expected_stderr : nullptr));
if (is_consumed) {
continue;
// `CHECK` lines are only expectations where autoupdate would write them.
// Everywhere else they're input, which is how a split can hold a test file
// that itself contains `CHECK` lines.
if (!uses_autoupdate_split || split_state.filename == AutoupdateSplit) {
CARBON_ASSIGN_OR_RETURN(
is_consumed,
TryConsumeCheck(running_autoupdate, line_index, line, line_trimmed,
test_file ? &test_file->expected_stdout : nullptr,
test_file ? &test_file->expected_stderr : nullptr));
if (is_consumed) {
continue;
}
}
if (test_file) {
@@ -895,8 +921,6 @@ auto ProcessTestFile(llvm::StringRef test_name, bool running_autoupdate)
return ErrorBuilder() << "Missing AUTOUPDATE/NOAUTOUPDATE setting";
}
constexpr llvm::StringLiteral AutoupdateSplit = "AUTOUPDATE-SPLIT";
// Validate AUTOUPDATE-SPLIT use, and remove it from test files if present.
if (test_file.has_splits) {
for (const auto& test_file :
@@ -0,0 +1,30 @@
// 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
// AUTOUPDATE
// TIP: To test this file alone, run:
// TIP: bazel test //testing/file_test:file_test_base_test --test_arg=--file_tests=testing/file_test/testdata/check_outside_autoupdate_split.carbon
// TIP: To dump output, run:
// TIP: bazel run //testing/file_test:file_test_base_test -- --dump_output --file_tests=testing/file_test/testdata/check_outside_autoupdate_split.carbon
// When the file uses an `AUTOUPDATE-SPLIT`, `CHECK` lines elsewhere are
// content rather than expectations, so a split can hold a test file of its
// own. Such a split still can't contain a literal `// ---` line, so the `/`
// characters are written as `[[@0x2f]]`.
//
// The echoed line numbers below show that both lines are part of `a.carbon`:
// if the `CHECK` had been consumed it would be an unmatched expectation, and
// if the `// ---` had split, there would be a second file in the arguments.
// --- a.carbon
// CHECK:STDOUT: not an expectation
this line follows a CHECK line
[[@0x2f]][[@0x2f]] --- not a split
this line follows a split marker
// --- AUTOUPDATE-SPLIT
// CHECK:STDOUT: 2 args: `default_args`, `a.carbon`
// CHECK:STDOUT: a.carbon:2: this line follows a CHECK line
// CHECK:STDOUT: a.carbon:4: this line follows a split marker
+36 -20
View File
@@ -18,28 +18,10 @@ import shlex
import subprocess
import sys
from pathlib import Path
from typing import Any
def main() -> None:
# Parse arguments.
parser = argparse.ArgumentParser(__doc__)
parser.add_argument("--non-fatal-checks", action="store_true")
parser.add_argument(
"--print_slowest_tests", default=0, help="Forwarded to file_test"
)
parser.add_argument("--threads", help="Forwarded to file_test")
parser.add_argument(
"--verbose", "-v", action="store_true", help="Produce verbose output"
)
parser.add_argument("files", nargs="*")
args = parser.parse_args()
printv = print if args.verbose else lambda _: None
bazel = str(Path(__file__).parents[1] / "scripts" / "run_bazel.py")
configs = []
# Use the most recently used build mode, or `fastbuild` if missing
# `bazel-bin`.
def _detect_build_mode(bazel: str, printv: Any) -> str:
build_mode = "fastbuild"
workspace = subprocess.check_output(
[
@@ -64,6 +46,40 @@ def main() -> None:
"Detected --compilation_mode: none (no `./bazel-bin`), "
+ f"falling back to {build_mode}"
)
return build_mode
def main() -> None:
# Parse arguments.
parser = argparse.ArgumentParser(__doc__)
parser.add_argument("--non-fatal-checks", action="store_true")
parser.add_argument(
"--print_slowest_tests", default=0, help="Forwarded to file_test"
)
parser.add_argument("--threads", help="Forwarded to file_test")
parser.add_argument(
"--verbose", "-v", action="store_true", help="Produce verbose output"
)
parser.add_argument(
"--compilation-mode",
"-c",
help=(
"Compilation mode to use. The default is to detect the mode used "
"by the last bazel invocation"
),
choices=["dbg", "fastbuild", "opt"],
)
parser.add_argument("files", nargs="*")
args = parser.parse_args()
printv = print if args.verbose else lambda _: None
bazel = str(Path(__file__).parents[1] / "scripts" / "run_bazel.py")
configs = []
# Unless the user chose one explicitly, use the most recently-used build
# mode, or `fastbuild` if missing `bazel-bin`.
build_mode = args.compilation_mode or _detect_build_mode(bazel, printv)
if args.non_fatal_checks:
if build_mode == "optimize":
+6 -9
View File
@@ -554,16 +554,13 @@ auto SourceGen::AppendUniqueIdentifiers(
// Append all the identifiers directly out of the set. We make no guarantees
// about the relative order so we just use the non-deterministic order of the
// set and avoid additional storage.
//
// TODO: It's awkward the `ForEach` here can't early-exit. This just walks the
// whole set which is harmless if inefficient. We should add early exiting
// the loop support to `Set` and update this code.
unique_idents.ForEach([&](llvm::StringRef ident) {
if (number > 0) {
dest.push_back(ident);
--number;
for (llvm::StringRef ident : unique_idents.entries()) {
if (number == 0) {
break;
}
});
dest.push_back(ident);
--number;
}
CARBON_CHECK(number == 0);
}
+216 -94
View File
@@ -18,18 +18,9 @@
namespace Carbon::Check {
auto PerformAction(Context& context, SemIR::SpecificId specific_id,
SemIR::LocId loc_id, SemIR::RefineInstAction action)
-> SemIR::InstId {
return AddInst<SemIR::SpecificInst>(
context, loc_id,
{.type_id = GetTypeOfInstInSpecific(context.sem_ir(), specific_id,
action.inst_id),
.inst_id = action.inst_id,
.specific_id = specific_id});
}
static auto OperandDependence(Context& context, SemIR::ConstantId const_id)
static auto OperandDependenceInSpecific(Context& context,
SemIR::SpecificId /*specific_id*/,
SemIR::ConstantId const_id)
-> SemIR::ConstantDependence {
// A type operand makes the instruction dependent if it is a
// template-dependent constant.
@@ -39,35 +30,73 @@ static auto OperandDependence(Context& context, SemIR::ConstantId const_id)
return context.constant_values().GetSymbolicConstant(const_id).dependence;
}
auto OperandDependence(Context& context, SemIR::TypeId type_id)
static auto OperandDependenceInSpecific(Context& context,
SemIR::SpecificId specific_id,
SemIR::TypeId type_id)
-> SemIR::ConstantDependence {
// A type operand makes the instruction dependent if it is a
// template-dependent type.
return OperandDependence(context, context.types().GetConstantId(type_id));
return OperandDependenceInSpecific(context, specific_id,
context.types().GetConstantId(type_id));
}
auto OperandDependence(Context& context, SemIR::InstId inst_id)
auto OperandDependence(Context& context, SemIR::TypeId type_id)
-> SemIR::ConstantDependence {
return OperandDependenceInSpecific(context, SemIR::SpecificId::None, type_id);
}
static auto OperandDependenceInSpecific(Context& context,
SemIR::SpecificId specific_id,
SemIR::InstId inst_id)
-> SemIR::ConstantDependence {
// An instruction operand makes the instruction dependent if its type or
// constant value is dependent.
return std::max(
OperandDependence(context, context.insts().Get(inst_id).type_id()),
OperandDependence(context, context.constant_values().Get(inst_id)));
OperandDependenceInSpecific(context, specific_id,
context.insts().Get(inst_id).type_id()),
OperandDependenceInSpecific(context, specific_id,
context.constant_values().Get(inst_id)));
}
static auto OperandDependence(Context& context, SemIR::MetaInstId inst_id)
auto OperandDependence(Context& context, SemIR::InstId inst_id)
-> SemIR::ConstantDependence {
return OperandDependenceInSpecific(context, SemIR::SpecificId::None, inst_id);
}
static auto OperandDependenceInSpecific(Context& context,
SemIR::SpecificId specific_id,
SemIR::MetaInstId inst_id)
-> SemIR::ConstantDependence {
// A meta-instruction operand makes the instruction dependent if its type or
// constant value is dependent.
return OperandDependence(context, SemIR::InstId{inst_id});
// constant value is dependent in this specific.
return std::max(
OperandDependenceInSpecific(
context, specific_id,
GetTypeOfInstInSpecific(context.sem_ir(), specific_id, inst_id)),
OperandDependenceInSpecific(context, specific_id,
SemIR::GetConstantValueInSpecific(
context.sem_ir(), specific_id, inst_id)));
}
auto OperandDependence(Context& context, SemIR::TypeInstId inst_id)
auto OperandDependence(Context& context, SemIR::MetaInstId inst_id)
-> SemIR::ConstantDependence {
return OperandDependenceInSpecific(context, SemIR::SpecificId::None, inst_id);
}
static auto OperandDependenceInSpecific(Context& context,
SemIR::SpecificId specific_id,
SemIR::TypeInstId inst_id)
-> SemIR::ConstantDependence {
// An instruction operand makes the instruction dependent if its type or
// constant value is dependent. TypeInstId has type `TypeType` which is
// concrete, so we only need to look at the constant value.
return OperandDependence(context, context.constant_values().Get(inst_id));
return OperandDependenceInSpecific(context, specific_id,
context.constant_values().Get(inst_id));
}
auto OperandDependence(Context& context, SemIR::TypeInstId inst_id)
-> SemIR::ConstantDependence {
return OperandDependenceInSpecific(context, SemIR::SpecificId::None, inst_id);
}
template <typename IdT>
@@ -76,75 +105,83 @@ template <typename IdT>
SemIR::CallParamIndex, SemIR::NameId,
SemIR::ElementIndex, SemIR::ClangDeclId,
SemIR::BoolValue>
static auto OperandDependence(Context& /*context*/, IdT /*id*/)
static auto OperandDependenceInSpecific(Context& /*context*/,
SemIR::SpecificId /*specific_id*/,
IdT /*id*/)
-> SemIR::ConstantDependence {
return SemIR::ConstantDependence::None;
}
template <typename BundleT>
static auto OperandDependence(Context& context,
SemIR::BundleId<BundleT> bundle_id)
static auto OperandDependenceInSpecific(Context& context,
SemIR::SpecificId specific_id,
SemIR::BundleId<BundleT> bundle_id)
-> SemIR::ConstantDependence {
return std::apply(
[&](auto... ids) {
return std::max({OperandDependence(context, ids)...});
return std::max(
{OperandDependenceInSpecific(context, specific_id, ids)...});
},
context.bundles().GetAsTuple(bundle_id));
}
static auto OperandDependence(Context& context,
SemIR::InstBlockId inst_block_id)
static auto OperandDependenceInSpecific(Context& context,
SemIR::SpecificId specific_id,
SemIR::InstBlockId inst_block_id)
-> SemIR::ConstantDependence {
auto result = SemIR::ConstantDependence::None;
for (auto arg_id : context.inst_blocks().Get(inst_block_id)) {
result = std::max(result, OperandDependence(context, arg_id));
result = std::max(
result, OperandDependenceInSpecific(context, specific_id, arg_id));
}
return result;
}
static auto OperandDependence(Context& context,
SemIR::MetaInstBlockId inst_block_id)
static auto OperandDependenceInSpecific(Context& context,
SemIR::SpecificId specific_id,
SemIR::MetaInstBlockId inst_block_id)
-> SemIR::ConstantDependence {
return OperandDependence(context, SemIR::InstBlockId{inst_block_id});
auto result = SemIR::ConstantDependence::None;
for (auto arg_id : context.inst_blocks().Get(inst_block_id)) {
result =
std::max(result, OperandDependenceInSpecific(
context, specific_id, SemIR::MetaInstId(arg_id)));
}
return result;
}
static auto OperandDependence(Context& context, SemIR::SpecificId specific_id)
static auto OperandDependenceInSpecific(Context& context,
SemIR::SpecificId specific_id,
SemIR::SpecificId inner_specific_id)
-> SemIR::ConstantDependence {
auto specific = context.specifics().Get(specific_id);
return OperandDependence(context, specific.args_id);
auto specific = context.specifics().Get(inner_specific_id);
return OperandDependenceInSpecific(context, specific_id, specific.args_id);
}
template <typename IdT>
requires SemIR::Internal::IsIdKindType<IdT>
static auto OperandDependence(Context& /*context*/, IdT /*id*/)
static auto OperandDependenceInSpecific(Context& /*context*/,
SemIR::SpecificId /*specific_id*/,
IdT /*id*/)
-> SemIR::ConstantDependence {
// TODO: Properly handle different argument kinds.
CARBON_FATAL("Unexpected argument kind for action: {}", IdT::Label);
}
static auto OperandDependence(Context& context, SemIR::IdAndKind arg)
static auto OperandDependenceInSpecific(Context& context,
SemIR::SpecificId specific_id,
SemIR::IdAndKind arg)
-> SemIR::ConstantDependence {
return arg.Dispatch<SemIR::ConstantDependence>(
[&](auto id) { return OperandDependence(context, id); });
return arg.Dispatch<SemIR::ConstantDependence>([&](auto id) {
return OperandDependenceInSpecific(context, specific_id, id);
});
}
auto ActionIsPerformable(Context& context, SemIR::Inst action_inst,
SemIR::SpecificId specific_id) -> bool {
if (auto refine_action = action_inst.TryAs<SemIR::RefineInstAction>()) {
// `RefineInstAction` is performable once the instruction's type and
// constant value are not template-dependent.
return OperandDependence(
context, GetTypeOfInstInSpecific(context.sem_ir(), specific_id,
refine_action->inst_id)) <
SemIR::ConstantDependence::Template &&
OperandDependence(context, SemIR::GetConstantValueInSpecific(
context.sem_ir(), specific_id,
refine_action->inst_id)) <
SemIR::ConstantDependence::Template;
}
// A form-parameterized action is performable if we can see at least the top
// level of its form's structure (i.e. it is not an action or a splice).
// TODO: Can we represent this as a different operand type instead?
if (auto form_parameterized_action =
action_inst.TryAs<SemIR::AnyFormParamAction>()) {
auto form_const_id =
@@ -167,32 +204,17 @@ auto ActionIsPerformable(Context& context, SemIR::Inst action_inst,
}
}
return OperandDependence(context, action_inst.type_id()) <
return OperandDependenceInSpecific(context, specific_id,
action_inst.type_id()) <
SemIR::ConstantDependence::Template &&
OperandDependence(context, action_inst.arg0_and_kind()) <
OperandDependenceInSpecific(context, specific_id,
action_inst.arg0_and_kind()) <
SemIR::ConstantDependence::Template &&
OperandDependence(context, action_inst.arg1_and_kind()) <
OperandDependenceInSpecific(context, specific_id,
action_inst.arg1_and_kind()) <
SemIR::ConstantDependence::Template;
}
static auto AddDependentActionSpliceImpl(Context& context,
SemIR::LocIdAndInst action,
SemIR::TypeInstId result_type_inst_id)
-> SemIR::InstId {
auto inst_id = AddDependentActionInst(context, action);
if (!result_type_inst_id.has_value()) {
result_type_inst_id =
AddTypeInst(context, action.loc_id,
SemIR::TypeOfInst{.type_id = SemIR::TypeType::TypeId,
.inst_id = inst_id});
}
return AddInst(
context, action.loc_id,
SemIR::SpliceInst{.type_id = context.types().GetTypeIdForTypeInstId(
result_type_inst_id),
.inst_id = inst_id});
}
// Refine one operand of an action. Given an argument from a template, this
// produces an argument that has the template-dependent parts replaced with
// their concrete values, so that the action doesn't need to know which specific
@@ -206,10 +228,10 @@ static auto RefineTypedOperand(Context& /*context*/, SemIR::LocId /*loc_id*/,
return id;
}
static auto RefineTypedOperand(Context& context, SemIR::LocId loc_id,
static auto RefineTypedOperand(Context& context, SemIR::LocId /*loc_id*/,
SemIR::MetaInstId inst_id) -> SemIR::MetaInstId {
auto inst = context.insts().Get(inst_id);
if (inst.Is<SemIR::SpliceInst>()) {
// TODO: Can we delete this check?
if (context.insts().Is<SemIR::SpliceInst>(inst_id)) {
// The argument will evaluate to the spliced instruction, which is already
// refined.
return inst_id;
@@ -223,23 +245,9 @@ static auto RefineTypedOperand(Context& context, SemIR::LocId loc_id,
return GetOrAddInstWithSpecificConstantValue(context, inst_id);
}
// If the type or constant value of the action argument is dependent, refine
// to an instruction with the type and value from the specific.
if (OperandDependence(context, inst.type_id()) ==
SemIR::ConstantDependence::Template ||
OperandDependence(context, const_id) ==
SemIR::ConstantDependence::Template) {
auto type_inst_id = context.types().GetTypeInstId(inst.type_id());
inst_id = AddDependentActionSpliceImpl(
context,
SemIR::LocIdAndInst(
loc_id,
SemIR::RefineInstAction{.type_id = GetSingletonType(
context, SemIR::InstType::TypeInstId),
.inst_id = inst_id}),
type_inst_id);
}
// If the type or constant value of the operand is template-dependent, it will
// be refined when the action is performed, once we know which specific we're
// performing it in.
return inst_id;
}
@@ -320,7 +328,121 @@ auto AddDependentActionSplice(Context& context, SemIR::LocIdAndInst action,
SemIR::TypeInstId result_type_inst_id)
-> SemIR::InstId {
action.inst = RefineOperands(context, action.loc_id, action.inst);
return AddDependentActionSpliceImpl(context, action, result_type_inst_id);
auto inst_id = AddDependentActionInst(context, action);
if (!result_type_inst_id.has_value()) {
result_type_inst_id =
AddTypeInst(context, action.loc_id,
SemIR::TypeOfInst{.type_id = SemIR::TypeType::TypeId,
.inst_id = inst_id});
}
return AddInst(
context, action.loc_id,
SemIR::SpliceInst{.type_id = context.types().GetTypeIdForTypeInstId(
result_type_inst_id),
.inst_id = inst_id});
}
// Refine one operand of an action that is being performed within a specific.
// Given an operand of an action from a generic, this produces an operand that
// refers to the corresponding instruction within the specific, so that the
// action doesn't need to know which specific it is operating on.
//
// This is the default case, for ID kinds that never need to be refined.
template <typename IdT>
requires SemIR::Internal::IsIdKindType<IdT>
static auto RefineTypedOperandInSpecific(Context& /*context*/,
SemIR::SpecificId /*specific_id*/,
SemIR::LocId /*loc_id*/, IdT id)
-> IdT {
return id;
}
static auto RefineTypedOperandInSpecific(Context& context,
SemIR::SpecificId specific_id,
SemIR::LocId loc_id,
SemIR::MetaInstId inst_id)
-> SemIR::MetaInstId {
// If the operand isn't template-dependent within the generic, then either it
// doesn't depend on the specific at all, or evaluation has already replaced
// it with the corresponding instruction from the specific.
if (OperandDependence(context, inst_id) !=
SemIR::ConstantDependence::Template) {
return inst_id;
}
// Produce an instruction with the same meaning as `inst_id`, but with the
// type and constant value that it has within the specific. This is added to
// the block of instructions produced by the action, so that it's evaluated
// before the instructions that use it.
return AddInst<SemIR::SpecificInst>(
context, loc_id,
{.type_id =
GetTypeOfInstInSpecific(context.sem_ir(), specific_id, inst_id),
.inst_id = inst_id,
.specific_id = specific_id});
}
static auto RefineTypedOperandInSpecific(Context& context,
SemIR::SpecificId specific_id,
SemIR::LocId loc_id,
SemIR::MetaInstBlockId inst_block_id)
-> SemIR::MetaInstBlockId {
auto block = context.inst_blocks().Get(inst_block_id);
llvm::SmallVector<SemIR::InstId> new_block;
new_block.reserve(block.size());
bool any_changed = false;
for (auto inst_id : block) {
new_block.push_back(RefineTypedOperandInSpecific(
context, specific_id, loc_id, SemIR::MetaInstId(inst_id)));
any_changed |= new_block.back() != inst_id;
}
if (!any_changed) {
return inst_block_id;
}
return SemIR::MetaInstBlockId(context.inst_blocks().AddCanonical(new_block));
}
template <typename BundleT>
static auto RefineTypedOperandInSpecific(Context& context,
SemIR::SpecificId specific_id,
SemIR::LocId loc_id,
SemIR::BundleId<BundleT> bundle_id)
-> SemIR::BundleId<BundleT> {
auto bundle_tuple = context.bundles().GetAsTuple(bundle_id);
BundleT refined_bundle = std::apply(
[&](auto... bundle_fields) {
// This can't actually recurse, because bundles can't contain bundle
// IDs.
return BundleT{RefineTypedOperandInSpecific(context, specific_id,
loc_id, bundle_fields)...};
},
bundle_tuple);
return context.bundles().AddCanonical(refined_bundle);
}
// Dynamically dispatched wrapper for RefineTypedOperandInSpecific.
static auto RefineOperandInSpecific(Context& context,
SemIR::SpecificId specific_id,
SemIR::LocId loc_id, SemIR::IdAndKind arg)
-> int32_t {
return arg.Dispatch<int32_t>([&](auto id) {
return SemIR::ToRaw(
RefineTypedOperandInSpecific(context, specific_id, loc_id, id));
});
}
auto Internal::RefineOperandsInSpecific(Context& context,
SemIR::SpecificId specific_id,
SemIR::LocId loc_id, SemIR::Inst action)
-> SemIR::Inst {
auto arg0 = RefineOperandInSpecific(context, specific_id, loc_id,
action.arg0_and_kind());
auto arg1 = RefineOperandInSpecific(context, specific_id, loc_id,
action.arg1_and_kind());
action.SetArgs(arg0, arg1);
return action;
}
auto Internal::BeginPerformDelayedAction(Context& context) -> void {
+30 -6
View File
@@ -74,6 +74,8 @@ auto ActionIsPerformable(Context& context, SemIR::Inst action_inst,
// constant-dependences of its type and its value).
auto OperandDependence(Context& context, SemIR::InstId inst_id)
-> SemIR::ConstantDependence;
auto OperandDependence(Context& context, SemIR::MetaInstId inst_id)
-> SemIR::ConstantDependence;
auto OperandDependence(Context& context, SemIR::TypeInstId inst_id)
-> SemIR::ConstantDependence;
@@ -153,11 +155,32 @@ namespace Internal {
// directly.
auto BeginPerformDelayedAction(Context& context) -> void;
// Calls the PerformAction function for the given action, passing in the
// relevant arguments.
template <typename ActionT>
auto CallPerformAction(Context& context, SemIR::SpecificId specific_id,
SemIR::LocId loc_id, ActionT action_inst)
-> SemIR::InstId {
if constexpr (ActionT::Kind.action_needs_specific_id()) {
return PerformAction(context, specific_id, loc_id, action_inst);
} else {
return PerformAction(context, loc_id, action_inst);
}
}
// Performs cleanup steps for performing a delayed action. This is an
// implementation detail of PerformDelayedAction and should not be called
// directly.
auto EndPerformDelayedAction(Context& context, SemIR::InstId result_id)
-> SemIR::InstId;
// Refines the operands of an action that is about to be performed within
// `specific_id`, so that they refer to instructions in the specific rather
// than in the generic. This is an implementation detail of
// PerformDelayedAction and should not be called directly.
auto RefineOperandsInSpecific(Context& context, SemIR::SpecificId specific_id,
SemIR::LocId loc_id, SemIR::Inst action)
-> SemIR::Inst;
} // namespace Internal
// Performs an action as a result of evaluation of a template's eval block.
@@ -169,12 +192,13 @@ auto PerformDelayedAction(Context& context, SemIR::SpecificId specific_id,
return SemIR::InstId::None;
}
Internal::BeginPerformDelayedAction(context);
auto inst_id = SemIR::InstId::None;
if constexpr (ActionT::Kind.action_needs_specific_id()) {
inst_id = PerformAction(context, specific_id, loc_id, action_inst);
} else {
inst_id = PerformAction(context, loc_id, action_inst);
}
// Refine the operands of the action so that they refer to instructions in
// the specific rather than in the generic. Any instructions this creates are
// included in the block of instructions produced by the action.
SemIR::Inst refined_action = Internal::RefineOperandsInSpecific(
context, specific_id, loc_id, action_inst);
auto inst_id = Internal::CallPerformAction(context, specific_id, loc_id,
refined_action.As<ActionT>());
return Internal::EndPerformDelayedAction(context, inst_id);
}
+24 -11
View File
@@ -42,9 +42,13 @@ enum class EntityKind : uint8_t {
} // namespace
// Resolves the callee expression in a call to a specific callee, or diagnoses
// if no specific callee can be identified. This verifies the arity of the
// callee and determines any compile-time arguments, but doesn't check that the
// runtime arguments are convertible to the parameter types.
// if no specific callee can be identified. This determines any compile-time
// arguments, but doesn't check that the runtime arguments are convertible to
// the parameter types. It also verifies that the number of arguments is within
// the range [callee_arity - arity_lower_bound_margin, callee_arity]. This
// allows arity matching when the callee has default arguments for some
// subpatterns. In all other cases supply the default value `0` for exact arity
// checking.
//
// `self_id` and `arg_ids` are the self argument and explicit arguments in the
// call.
@@ -56,14 +60,21 @@ static auto ResolveCalleeInCall(Context& context, SemIR::LocId loc_id,
EntityKind entity_kind_for_diagnostic,
SemIR::SpecificId enclosing_specific_id,
SemIR::InstId self_id,
llvm::ArrayRef<SemIR::InstId> arg_ids)
llvm::ArrayRef<SemIR::InstId> arg_ids,
int32_t arity_lower_bound_margin = 0)
-> std::optional<SemIR::SpecificId> {
// Check that the arity matches the explicit arguments.
// Check that the arity exactly matches or is the upper bound of the explicit
// arguments.
auto param_patterns =
context.inst_blocks().GetOrEmpty(entity.param_patterns_id);
size_t expected_args_size =
param_patterns.size() - (self_id.has_value() ? 1 : 0);
if (arg_ids.size() != expected_args_size) {
CARBON_CHECK(static_cast<size_t>(arity_lower_bound_margin) <=
expected_args_size);
size_t size_lower_bound =
expected_args_size - static_cast<size_t>(arity_lower_bound_margin);
if (arg_ids.size() < size_lower_bound ||
arg_ids.size() > expected_args_size) {
CARBON_DIAGNOSTIC(CallArgCountMismatch, Error,
"{0} argument{0:s} passed to "
"{1:=0:function|=1:generic class|=2:generic "
@@ -219,11 +230,13 @@ auto PerformCallToFunction(Context& context, SemIR::LocId loc_id,
llvm::ArrayRef<SemIR::InstId> arg_ids,
bool is_desugared) -> SemIR::InstId {
// If the callee is a generic function, determine the generic argument values
// for the call.
// for the call. Also check the arity of the function against the arguments,
// with allowance for default argument values.
const auto& function = context.functions().Get(callee_function.function_id);
auto callee_specific_id = ResolveCalleeInCall(
context, loc_id, context.functions().Get(callee_function.function_id),
EntityKind::Function, callee_function.enclosing_specific_id,
callee_function.self_id, arg_ids);
context, loc_id, function, EntityKind::Function,
callee_function.enclosing_specific_id, callee_function.self_id, arg_ids,
function.default_value_arity);
if (!callee_specific_id) {
return SemIR::ErrorInst::InstId;
}
@@ -297,7 +310,7 @@ auto PerformCallToFunction(Context& context, SemIR::LocId loc_id,
case SemIR::Function::SpecialFunctionKind::None:
case SemIR::Function::SpecialFunctionKind::Builtin:
case SemIR::Function::SpecialFunctionKind::CoreWitness:
case SemIR::Function::SpecialFunctionKind::Generated:
case SemIR::Function::SpecialFunctionKind::CppThunk: {
return GetOrAddInst<SemIR::Call>(context, loc_id,
{.type_id = return_type_id,
+9 -10
View File
@@ -507,22 +507,21 @@ auto CheckParseTrees(
// Create C++ domains for Cpp imports.
if (options.share_cpp_ast) {
bool any_cpp_imports = false;
llvm::SmallVector<SemIR::CppInputFile> inputs;
for (auto& unit_info : unit_infos) {
if (unit_info.cpp_imports.empty()) {
continue;
}
any_cpp_imports |= !unit_info.cpp_imports.empty();
inputs.push_back({.check_ir_id = unit_info.unit->sem_ir->check_ir_id(),
.filename = unit_info.unit->sem_ir->filename(),
.is_lowered = unit_info.unit->is_lowered});
}
// TODO: Remove dependence on properties of the first unit here.
if (auto cpp_domain = InitializeCppDomain(
unit_infos.front().err_tracker, inputs, fs,
unit_infos.front().unit->llvm_context, clang_invocation)) {
cpp_domains.push_back(std::move(cpp_domain));
for (auto& unit_info : unit_infos) {
if (!unit_info.cpp_imports.empty()) {
if (any_cpp_imports) {
// TODO: Remove dependence on properties of the first unit here.
if (auto cpp_domain = InitializeCppDomain(
unit_infos.front().err_tracker, inputs, fs,
unit_infos.front().unit->llvm_context, clang_invocation)) {
cpp_domains.push_back(std::move(cpp_domain));
for (auto& unit_info : unit_infos) {
unit_info.cpp_domain = cpp_domains.back().get();
}
}
+27 -8
View File
@@ -174,13 +174,15 @@ static auto CompareVirtualWithOverrider(const SemIR::Function& base_fn,
return OverrideMatchResult::Match;
}
// Builds and returns a vtable for the current class. Assumes that the virtual
// functions for the class are listed as the top element of the `vtable_stack`.
// Builds and returns a vtable for the current class, along with a bool
// indicating whether it is a Carbon-native vtable (false for a foreign vtable
// inherited from a C++ base class). Assumes that the virtual functions for the
// class are listed as the top element of the `vtable_stack`.
static auto BuildVtable(Context& context, Parse::ClassDefinitionId node_id,
SemIR::ClassId class_id,
std::optional<SemIR::ClassType> base_class_type,
llvm::ArrayRef<SemIR::InstId> vtable_contents)
-> SemIR::VtableId {
-> std::pair<SemIR::VtableId, bool> {
auto base_vtable_id = SemIR::VtableId::None;
auto base_class_specific_id = SemIR::SpecificId::None;
@@ -222,7 +224,7 @@ static auto BuildVtable(Context& context, Parse::ClassDefinitionId node_id,
};
llvm::SmallVector<SemIR::InstId> vtable;
Set<SemIR::FunctionId> implemented_impls;
Set<SemIR::FunctionId, 16> implemented_impls;
bool carbon_native_vtable = true;
// Add vtable entries from the base class, updating them to point to a derived
@@ -358,10 +360,11 @@ static auto BuildVtable(Context& context, Parse::ClassDefinitionId node_id,
}
}
return context.vtables().Add(
auto vtable_id = context.vtables().Add(
{{.class_id = class_id,
.virtual_functions_id = context.inst_blocks().Add(vtable),
.carbon_native_vtable = carbon_native_vtable}});
return {vtable_id, carbon_native_vtable};
}
// Checks that the specified finished class definition is valid and builds and
@@ -411,9 +414,11 @@ static auto CheckCompleteClassType(
{.name_id = SemIR::NameId::Base, .type_inst_id = base_type_inst_id});
}
bool foreign_vtable = false;
if (class_info.is_dynamic) {
auto vtable_id = BuildVtable(context, node_id, class_id, base_class_type,
vtable_contents);
auto [vtable_id, carbon_native_vtable] = BuildVtable(
context, node_id, class_id, base_class_type, vtable_contents);
foreign_vtable = !carbon_native_vtable;
auto vptr_type_id = GetPointerType(context, SemIR::VtableType::TypeInstId);
class_info.vtable_decl_id = AddInst<SemIR::VtableDecl>(
context, node_id, {.type_id = vptr_type_id, .vtable_id = vtable_id});
@@ -422,11 +427,25 @@ static auto CheckCompleteClassType(
auto struct_type_id = GetStructType(
context, AddStructTypeFields(context, struct_type_fields, field_decls));
return AddInst<SemIR::CompleteTypeWitness>(
auto complete_type_witness_id = AddInst<SemIR::CompleteTypeWitness>(
context, node_id,
{.type_id = GetSingletonType(context, SemIR::WitnessType::TypeInstId),
.object_repr_type_inst_id =
context.types().GetTypeInstId(struct_type_id)});
class_info.complete_type_witness_id = complete_type_witness_id;
if (foreign_vtable) {
if (class_info.generic_id.has_value()) {
context.TODO(class_info.first_decl_id(),
"generic class deriving from C++ virtual class");
} else {
ExportAndCompleteClassToCpp(
context,
context.types().GetAs<SemIR::ClassType>(class_info.self_type_id));
}
}
return complete_type_witness_id;
}
auto ComputeClassObjectRepr(Context& context, Parse::ClassDefinitionId node_id,
+15
View File
@@ -352,6 +352,8 @@ class Context {
auto core_identifiers() -> CoreIdentifierCache& { return core_identifiers_; }
auto access_context() -> SemIR::NameScopeId& { return access_context_; }
// --------------------------------------------------------------------------
// Directly expose SemIR::File data accessors for brevity in calls.
// --------------------------------------------------------------------------
@@ -372,6 +374,9 @@ class Context {
return sem_ir().cpp_overload_sets();
}
auto functions() -> SemIR::FunctionStore& { return sem_ir().functions(); }
auto generated_functions() -> SemIR::GeneratedFunctionStore& {
return sem_ir().generated_functions();
}
auto thunks() -> SemIR::ThunkStore& { return sem_ir().thunks(); }
auto classes() -> SemIR::ClassStore& { return sem_ir().classes(); }
auto fields() -> SemIR::FieldStore& { return sem_ir().fields(); }
@@ -396,6 +401,9 @@ class Context {
auto declared_facet_types() -> SemIR::DeclaredFacetTypeStore& {
return sem_ir().declared_facet_types();
}
auto default_values() -> SemIR::DefaultValueStore& {
return sem_ir().default_values();
}
auto identified_facet_types() -> SemIR::IdentifiedFacetTypeStore& {
return sem_ir().identified_facet_types();
}
@@ -623,6 +631,13 @@ class Context {
CoreIdentifierCache core_identifiers_;
bool mangle_string_fingerprint_;
// Scope for querying member access. For example, when checking a class
// method, this would be set to the scope of that method's class.
//
// This is updated by `DeclNameStack`. During monomorphization, it is updated
// by `TryEvalBlockForSpecific`.
SemIR::NameScopeId access_context_ = SemIR::NameScopeId::None;
};
inline constexpr Context::FormExpr Context::FormExpr::Error = {
+3 -1
View File
@@ -157,7 +157,9 @@ static auto AddCleanups(Context& context, ScopeStack::CleanupScopeDepth depth)
// cleanup blocks, so we'll want to avoid this in the future.
BuildUnaryOperator(context,
context.insts().GetLocIdForDesugaring(destroy_id),
{.interface_name = CoreIdentifier::Destroy}, destroy_id);
{.interface_name = CoreIdentifier::Destroy,
.op_name = CoreIdentifier::SelfDestruct},
destroy_id);
}
}
+41 -40
View File
@@ -649,15 +649,15 @@ static auto ConvertStructToStructOrClass(
value_id = MaterializeIfInitializer(context, value_id);
}
Set<SemIR::NameId> dest_field_names;
for (auto field : dest_elem_fields) {
dest_field_names.Insert(field.name_id);
}
// Prepare to look up fields in the source by index. Also check for
// source fields that don't match any field in the destination.
Map<SemIR::NameId, int32_t> src_field_indexes;
if (src_type.fields_id != dest_type.fields_id) {
Set<SemIR::NameId, 16> dest_field_names;
for (auto field : dest_elem_fields) {
dest_field_names.Insert(field.name_id);
}
for (auto [i, field] : llvm::enumerate(src_elem_fields)) {
if (!dest_field_names.Lookup(field.name_id)) {
if (target.diagnose) {
@@ -1195,15 +1195,14 @@ static auto CanRemoveQualifiers(SemIR::TypeQualifiers quals,
static auto DiagnoseConversionFailureToConstraintValue(
Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id,
SemIR::TypeId target_type_id) -> void {
CARBON_CHECK(context.types().IsFacetType(target_type_id));
CARBON_CHECK(context.types().Is<SemIR::FacetType>(target_type_id));
// If the source type is/has a facet value (converted with `as type` or
// otherwise), then we can include its `FacetType` in the diagnostic to help
// explain what interfaces the source type implements.
auto const_expr_id = GetCanonicalFacetOrTypeValue(context, expr_id);
// If the source is a facet with constraints (possibly converted to `type`),
// then we can include those constraints in the diagnostic.
auto const_expr_id = GetCanonicalFacet(context, expr_id);
auto const_expr_type_id = context.insts().Get(const_expr_id).type_id();
if (context.types().Is<SemIR::FacetType>(const_expr_type_id)) {
if (context.types().IsConstrainedFacetType(const_expr_type_id)) {
CARBON_DIAGNOSTIC(ConversionFailureFacetToFacet, Error,
"cannot convert type {0} that implements {1} into type "
"implementing {2}",
@@ -1409,22 +1408,28 @@ static auto PerformBuiltinConversion(Context& context, SemIR::LocId loc_id,
}
}
value_id = AddInst<SemIR::AsCompatible>(
context, loc_id,
{.type_id = target.type_id, .source_id = value_id});
// An expression of type T converts to U if T is a class derived from U.
// First navigate to the base subobject. This preserves qualifiers.
if (inheritance_path) {
value_id = ConvertDerivedToBase(context, loc_id, value_id,
*inheritance_path);
}
// Next, switch out the qualifiers for those of the target.
if (context.insts().Get(value_id).type_id() != target.type_id) {
value_id = AddInst<SemIR::AsCompatible>(
context, loc_id,
{.type_id = target.type_id, .source_id = value_id});
}
// Finally, add a value acquisition to get back to a value expression if
// we temporarily converted to a reference earlier.
if (need_value_binding) {
value_id = AddInst<SemIR::AcquireValue>(
context, loc_id,
{.type_id = target.type_id, .value_id = value_id});
}
// An expression of type T converts to U if T is a class derived from U.
if (inheritance_path) {
value_id = ConvertDerivedToBase(context, loc_id, value_id,
*inheritance_path);
}
return value_id;
} else {
// TODO: Produce a custom diagnostic explaining that we can't perform
@@ -1550,7 +1555,7 @@ static auto PerformBuiltinConversion(Context& context, SemIR::LocId loc_id,
}
}
if (sem_ir.types().IsFacetType(target.type_id)) {
if (sem_ir.types().Is<SemIR::FacetType>(target.type_id)) {
auto type_value_id = SemIR::TypeInstId::None;
// A tuple of types converts to type `type`.
@@ -1568,7 +1573,7 @@ static auto PerformBuiltinConversion(Context& context, SemIR::LocId loc_id,
}
if (type_value_id != SemIR::InstId::None) {
if (sem_ir.types().Is<SemIR::FacetType>(target.type_id)) {
if (target.type_id != SemIR::TypeType::TypeId) {
// Use the converted `TypeType` value for converting to a facet.
value_id = type_value_id;
value_type_id = SemIR::TypeType::TypeId;
@@ -1579,21 +1584,18 @@ static auto PerformBuiltinConversion(Context& context, SemIR::LocId loc_id,
}
}
// FacetType converts to Type by wrapping the facet value in
// FacetAccessType.
// All facets convert to `type` by wrapping the facet in FacetAccessType.
if (target.type_id == SemIR::TypeType::TypeId &&
sem_ir.types().Is<SemIR::FacetType>(value_type_id)) {
sem_ir.types().IsConstrainedFacetType(value_type_id)) {
return AddInst<SemIR::FacetAccessType>(
context, loc_id,
{.type_id = target.type_id, .facet_value_inst_id = value_id});
}
// Type values can convert to facet values, and facet values can convert to
// other facet values, as long as they satisfy the required interfaces of the
// target `FacetType`.
if (sem_ir.types().Is<SemIR::FacetType>(target.type_id) &&
sem_ir.types().IsOneOf<SemIR::TypeType, SemIR::FacetType>(
value_type_id)) {
// All facets (including types) can convert into other facets, as long as they
// satisfy the constraints of the target `FacetType`.
if (sem_ir.types().IsConstrainedFacetType(target.type_id) &&
sem_ir.types().Is<SemIR::FacetType>(value_type_id)) {
// TODO: Runtime facet values should be allowed to convert based on their
// FacetTypes, but we assume constant values for impl lookup at the moment.
if (!context.constant_values().Get(value_id).is_constant()) {
@@ -1602,9 +1604,9 @@ static auto PerformBuiltinConversion(Context& context, SemIR::LocId loc_id,
}
// Get the canonical type for which we want to attach a new set of witnesses
// to match the requirements of the target FacetType.
// to match the requirements of the target `FacetType`.
auto type_inst_id = SemIR::TypeInstId::None;
if (sem_ir.types().Is<SemIR::FacetType>(value_type_id)) {
if (value_type_id != SemIR::TypeType::TypeId) {
type_inst_id = AddTypeInst<SemIR::FacetAccessType>(
context, loc_id,
{.type_id = SemIR::TypeType::TypeId,
@@ -1621,8 +1623,7 @@ static auto PerformBuiltinConversion(Context& context, SemIR::LocId loc_id,
// would evaluate back to the original SymbolicBinding as its canonical
// form. We can skip past the whole impl lookup step then and do that
// here.
auto facet_value_inst_id =
GetCanonicalFacetOrTypeValue(context, type_inst_id);
auto facet_value_inst_id = GetCanonicalFacet(context, type_inst_id);
if (sem_ir.insts().Get(facet_value_inst_id).type_id() == target.type_id) {
return facet_value_inst_id;
}
@@ -1736,8 +1737,7 @@ static auto PerformUserDefinedConversion(Context& context, SemIR::LocId loc_id,
target.kind == ConversionTarget::ExplicitAs ? 1
: target.kind == ConversionTarget::ExplicitUnsafeAs ? 2
: 0;
if (target.type_id == SemIR::TypeType::TypeId ||
context.types().Is<SemIR::FacetType>(target.type_id)) {
if (context.types().Is<SemIR::FacetType>(target.type_id)) {
CARBON_DIAGNOSTIC(
ConversionFailureNonTypeToFacet, Context,
"cannot{0:=0: implicitly|:} convert non-type value of type {1} "
@@ -2107,7 +2107,7 @@ static auto ConversionNeedsCompleteTarget(Context& context,
// We allow conversion to incomplete facet types, since their representation
// is fixed. This allows us to support using the `Self` of an interface inside
// its definition.
if (context.types().IsFacetType(target.type_id)) {
if (context.types().Is<SemIR::FacetType>(target.type_id)) {
return false;
}
@@ -2382,9 +2382,10 @@ auto ConvertCallArgs(Context& context, SemIR::InstId self_id,
SemIR::InstId return_arg_id, const SemIR::Function& callee,
SemIR::SpecificId callee_specific_id, bool is_desugared)
-> SemIR::InstBlockId {
// The caller should have ensured this callee has the right arity.
// The caller should have ensured this callee has the right arity, modulo
// default arguments.
CARBON_CHECK(
(self_id.has_value() ? 1 : 0) + arg_refs.size() ==
(self_id.has_value() ? 1 : 0) + arg_refs.size() <=
context.inst_blocks().GetOrEmpty(callee.param_patterns_id).size());
return CallerPatternMatch(context, callee_specific_id, callee.self_param_id,
+2
View File
@@ -76,9 +76,11 @@ CARBON_CORE_IDENTIFIER(Optional)
CARBON_CORE_IDENTIFIER(OrderedWith)
CARBON_CORE_IDENTIFIER(RightShiftAssignWith)
CARBON_CORE_IDENTIFIER(RightShiftWith)
CARBON_CORE_IDENTIFIER(SelfDestruct)
CARBON_CORE_IDENTIFIER(String)
CARBON_CORE_IDENTIFIER(SubAssignWith)
CARBON_CORE_IDENTIFIER(SubWith)
CARBON_CORE_IDENTIFIER(SubobjectDestroy)
CARBON_CORE_IDENTIFIER(UInt)
CARBON_CORE_IDENTIFIER(ULong32)
CARBON_CORE_IDENTIFIER(ULong64)
+3 -4
View File
@@ -30,10 +30,9 @@ namespace Carbon::Check {
static auto IsTemplateArg(Context& context, SemIR::InstId arg_id) -> bool {
auto arg_type_id = context.insts().Get(arg_id).type_id();
auto arg_type = context.types().GetAsInst(arg_type_id);
return arg_type
.IsOneOf<SemIR::TypeType, SemIR::FacetType, SemIR::CppTemplateNameType,
SemIR::GenericClassType, SemIR::GenericInterfaceType,
SemIR::GenericNamedConstraintType>();
return arg_type.IsOneOf<SemIR::FacetType, SemIR::CppTemplateNameType,
SemIR::GenericClassType, SemIR::GenericInterfaceType,
SemIR::GenericNamedConstraintType>();
}
// Splits a call argument list into a list of template arguments followed by a
+16 -5
View File
@@ -192,8 +192,9 @@ static auto CreateClassTemplateSpecializationDecl(
template_args,
/*StrictPackMatch=*/false,
/*PrevDecl=*/nullptr);
class_template_decl->AddSpecialization(class_template_specialization_decl,
/*InsertPos=*/nullptr);
class_template_decl->AddSpecialization(
class_template_specialization_decl,
/*InsertPos=*/llvm::FoldingSetInsertToken());
class_template_specialization_decl->setHasExternalLexicalStorage();
class_template_specialization_decl->setHasExternalVisibleStorage();
@@ -292,6 +293,16 @@ auto ExportClassToCpp(Context& context, SemIR::ClassType class_type)
return record_decl;
}
auto ExportAndCompleteClassToCpp(Context& context, SemIR::ClassType class_type)
-> clang::TagDecl* {
auto* tag_decl = ExportClassToCpp(context, class_type);
if (tag_decl && context.cpp_context() &&
context.ast_context().getExternalSource()) {
context.ast_context().getExternalSource()->CompleteType(tag_decl);
}
return tag_decl;
}
// Export the bindings in a generic as a `clang::TemplateParameterList`.
static auto ExportGenericBindings(Context& context, SemIR::LocId loc_id,
SemIR::GenericId generic_id,
@@ -321,8 +332,7 @@ static auto ExportGenericBindings(Context& context, SemIR::LocId loc_id,
CARBON_CHECK(param_ident, "non-identifier param name {0}",
entity_name.name_id);
if (symbolic_binding.type_id != SemIR::TypeType::TypeId &&
!context.types().Is<SemIR::FacetType>(symbolic_binding.type_id)) {
if (!context.types().Is<SemIR::FacetType>(symbolic_binding.type_id)) {
context.TODO(loc_id, "binding maps to a non-type template parameter");
return nullptr;
}
@@ -1451,7 +1461,8 @@ auto ExportFunctionSpecializationToCpp(
context.ast_context(), template_args);
function_decl->setFunctionTemplateSpecialization(
function_template_decl, template_arg_list,
/*InsertPos=*/nullptr, clang::TSK_ExplicitSpecialization,
/*InsertPos=*/llvm::FoldingSetInsertToken(),
clang::TSK_ExplicitSpecialization,
/*TemplateArgsAsWritten=*/nullptr,
/*PointOfInstantiation=*/clang::SourceLocation());
+5
View File
@@ -36,6 +36,11 @@ auto ExportNameScopeToCpp(Context& context, SemIR::LocId loc_id,
auto ExportClassToCpp(Context& context, SemIR::ClassType class_type)
-> clang::TagDecl*;
// Exports a dynamic Carbon class with a foreign (C++) vtable into C++ as a
// class, and completes its definition.
auto ExportAndCompleteClassToCpp(Context& context, SemIR::ClassType class_type)
-> clang::TagDecl*;
// Exports a generic Carbon class into C++ as a templated class.
//
// If the generic class has already been exported, returns the existing
+5 -1
View File
@@ -501,10 +501,14 @@ auto CarbonExternalASTSource::CompleteType(clang::TagDecl* tag_decl) -> void {
llvm::SmallVector<PendingVirtualFunction> pending_virtual_functions;
if (class_info.vtable_decl_id.has_value()) {
LoadImportRef(*context_, class_info.vtable_decl_id);
auto canonical_vtable_decl_id =
context_->constant_values().GetConstantInstId(
class_info.vtable_decl_id);
auto vtable_inst_block = context_->inst_blocks().Get(
context_->vtables()
.Get(context_->insts()
.GetAs<SemIR::VtableDecl>(class_info.vtable_decl_id)
.GetAs<SemIR::VtableDecl>(canonical_vtable_decl_id)
.vtable_id)
.virtual_functions_id);
for (auto vtable_entry_id : vtable_inst_block) {
+49 -55
View File
@@ -127,10 +127,10 @@ static auto MakeSignature(
modes, SemIR::ClangDeclSignature::Normal, self_passing_mode));
}
static auto BuildCopyWitness(
Context& context, SemIR::LocId loc_id,
SemIR::ConstantId query_self_const_id,
SemIR::SpecificInterfaceId query_specific_interface_id) -> SemIR::InstId {
static auto BuildCopyWitness(Context& context, SemIR::LocId loc_id,
SemIR::ConstantId query_self_const_id,
SemIR::SpecificInterface query_specific_interface)
-> SemIR::InstId {
auto& clang_sema = context.clang_sema();
auto* tag_decl = TypeAsTagDecl(context, query_self_const_id);
@@ -160,19 +160,18 @@ static auto BuildCopyWitness(
return fn_id;
}
return BuildCustomWitness(context, loc_id, query_self_const_id,
query_specific_interface_id, {fn_id});
query_specific_interface, {fn_id});
}
// Otherwise it's an enum (or eventually a C struct type). Perform a primitive
// copy.
return BuildPrimitiveCopyWitness(
context, loc_id, GetClassScope(context, query_self_const_id),
query_self_const_id, query_specific_interface_id);
return BuildPrimitiveCopyWitness(context, loc_id, query_self_const_id,
query_specific_interface);
}
static auto BuildCppUnsafeDerefWitness(
Context& context, SemIR::LocId loc_id,
SemIR::ConstantId query_self_const_id,
SemIR::SpecificInterfaceId query_specific_interface_id) -> SemIR::InstId {
SemIR::SpecificInterface query_specific_interface) -> SemIR::InstId {
auto& clang_sema = context.clang_sema();
auto* class_decl = TypeAsClassDecl(context, query_self_const_id);
@@ -207,7 +206,7 @@ static auto BuildCppUnsafeDerefWitness(
.Get(context.insts().GetAs<SemIR::FunctionDecl>(fn_id).function_id)
.return_type_inst_id;
return BuildCustomWitness(
context, loc_id, query_self_const_id, query_specific_interface_id,
context, loc_id, query_self_const_id, query_specific_interface,
{context.types().GetTypeInstId(context.types().GetUnqualifiedType(
context.types().GetTypeIdForTypeInstId(result_type_inst_id))),
fn_id});
@@ -216,7 +215,7 @@ static auto BuildCppUnsafeDerefWitness(
static auto BuildDefaultWitness(
Context& context, SemIR::LocId loc_id,
SemIR::ConstantId query_self_const_id,
SemIR::SpecificInterfaceId query_specific_interface_id) -> SemIR::InstId {
SemIR::SpecificInterface query_specific_interface) -> SemIR::InstId {
auto& clang_sema = context.clang_sema();
auto* class_decl = TypeAsClassDecl(context, query_self_const_id);
@@ -239,13 +238,13 @@ static auto BuildDefaultWitness(
return fn_id;
}
return BuildCustomWitness(context, loc_id, query_self_const_id,
query_specific_interface_id, {fn_id});
query_specific_interface, {fn_id});
}
static auto BuildDestroyWitness(
static auto BuildCppDestroyWitness(
Context& context, SemIR::LocId loc_id,
SemIR::ConstantId query_self_const_id,
SemIR::SpecificInterfaceId query_specific_interface_id) -> SemIR::InstId {
SemIR::SpecificInterface query_specific_interface) -> SemIR::InstId {
auto& clang_sema = context.clang_sema();
auto* tag_decl = TypeAsTagDecl(context, query_self_const_id);
@@ -255,7 +254,7 @@ static auto BuildDestroyWitness(
auto* class_decl = dyn_cast<clang::CXXRecordDecl>(tag_decl);
if (!class_decl) {
return BuildTrivialDestroyWitness(context, loc_id, query_self_const_id,
query_specific_interface_id);
query_specific_interface);
}
SemIR::ClangDeclSignatureId signature_id = MakeSignature(context, {});
@@ -265,15 +264,18 @@ static auto BuildDestroyWitness(
if (fn_id == SemIR::ErrorInst::InstId || fn_id == SemIR::InstId::None) {
return fn_id;
}
return BuildCustomWitness(context, loc_id, query_self_const_id,
query_specific_interface_id, {fn_id});
return BuildDestroyWitness(
context, loc_id,
GetFacetAccessType(
context, context.constant_values().GetInstId(query_self_const_id)),
query_self_const_id, query_specific_interface, {fn_id});
}
// Attempts to build a witness table entry for a C++ unary operator.
static auto BuildCppUnaryOperatorWitness(
Context& context, SemIR::LocId loc_id, SemIR::CoreInterface core_interface,
bool has_associated_result_type, SemIR::ConstantId query_self_const_id,
SemIR::SpecificInterfaceId query_specific_interface_id) -> SemIR::InstId {
SemIR::SpecificInterface query_specific_interface) -> SemIR::InstId {
auto self_type_id =
context.types().GetTypeIdForTypeConstantId(query_self_const_id);
auto fn_id = LookupCppOperator(
@@ -293,26 +295,22 @@ static auto BuildCppUnaryOperatorWitness(
}
return BuildCustomWitness(context, loc_id, query_self_const_id,
query_specific_interface_id,
query_specific_interface,
{result_type_id, fn_id});
}
return BuildCustomWitness(context, loc_id, query_self_const_id,
query_specific_interface_id, {fn_id});
query_specific_interface, {fn_id});
}
// Attempts to build a witness table entry for a C++ binary operator.
static auto BuildCppBinaryOperatorWitness(
Context& context, SemIR::LocId loc_id, SemIR::CoreInterface core_interface,
bool has_associated_result_type, SemIR::ConstantId query_self_const_id,
SemIR::SpecificInterfaceId query_specific_interface_id) -> SemIR::InstId {
SemIR::SpecificInterface query_specific_interface) -> SemIR::InstId {
auto self_type_id =
context.types().GetTypeIdForTypeConstantId(query_self_const_id);
auto args =
context.inst_blocks().Get(context.specifics()
.Get(context.specific_interfaces()
.Get(query_specific_interface_id)
.specific_id)
.args_id);
auto args = context.inst_blocks().Get(
context.specifics().Get(query_specific_interface.specific_id).args_id);
CARBON_CHECK(args.size() == 1, "Binary operator missing an argument");
auto arg_type_id = context.types().GetTypeIdForTypeInstId(args.front());
auto fn_id = LookupCppOperator(
@@ -330,26 +328,22 @@ static auto BuildCppBinaryOperatorWitness(
return SemIR::ErrorInst::InstId;
}
return BuildCustomWitness(context, loc_id, query_self_const_id,
query_specific_interface_id,
query_specific_interface,
{result_type_id, fn_id});
}
return BuildCustomWitness(context, loc_id, query_self_const_id,
query_specific_interface_id, {fn_id});
query_specific_interface, {fn_id});
}
static auto BuildCppComparisonWitness(
Context& context, SemIR::LocId loc_id, CoreIdentifier interface,
llvm::ArrayRef<CoreIdentifier> operator_names,
SemIR::ConstantId query_self_const_id,
SemIR::SpecificInterfaceId query_specific_interface_id) -> SemIR::InstId {
SemIR::SpecificInterface query_specific_interface) -> SemIR::InstId {
auto self_type_id =
context.types().GetTypeIdForTypeConstantId(query_self_const_id);
auto args =
context.inst_blocks().Get(context.specifics()
.Get(context.specific_interfaces()
.Get(query_specific_interface_id)
.specific_id)
.args_id);
auto args = context.inst_blocks().Get(
context.specifics().Get(query_specific_interface.specific_id).args_id);
CARBON_CHECK(args.size() == 1, "Binary operator missing an argument");
auto arg_type_id = context.types().GetTypeIdForTypeInstId(args[0]);
@@ -385,7 +379,7 @@ static auto BuildCppComparisonWitness(
}
return BuildCustomWitness(context, loc_id, query_self_const_id,
query_specific_interface_id, operators);
query_specific_interface, operators);
}
static auto LookupCppMethod(Context& context, clang::Sema& clang_sema,
@@ -497,7 +491,7 @@ static auto BuildCppRangeForIterateWitnessImpl(
Context& context, SemIR::LocId loc_id,
LookupBeginEndCallees range_for_lookup, clang::CXXRecordDecl* class_decl,
SemIR::ConstantId query_self_const_id,
SemIR::SpecificInterfaceId query_specific_interface_id) -> SemIR::InstId {
SemIR::SpecificInterface query_specific_interface) -> SemIR::InstId {
auto& clang_sema = context.clang_sema();
auto begin_name_info = clang::DeclarationNameInfo(
&clang_sema.PP.getIdentifierTable().get("begin"),
@@ -537,31 +531,31 @@ static auto BuildCppRangeForIterateWitnessImpl(
end_result_type_id != SemIR::InstId::None);
return BuildCustomWitness(
context, loc_id, query_self_const_id, query_specific_interface_id,
context, loc_id, query_self_const_id, query_specific_interface,
{begin_result_type_id, end_result_type_id, begin_fn_id, end_fn_id});
}
static auto BuildCppRangeForIterateWitness(
Context& context, SemIR::LocId loc_id,
SemIR::ConstantId query_self_const_id,
SemIR::SpecificInterfaceId query_specific_interface_id) -> SemIR::InstId {
SemIR::SpecificInterface query_specific_interface) -> SemIR::InstId {
auto* class_decl = TypeAsClassDecl(context, query_self_const_id);
if (auto with_members = BuildCppRangeForIterateWitnessImpl(
context, loc_id, LookupCppMethod, class_decl, query_self_const_id,
query_specific_interface_id);
query_specific_interface);
with_members != SemIR::InstId::None) {
return with_members;
}
return BuildCppRangeForIterateWitnessImpl(
context, loc_id, LookupCppUnqualified, class_decl, query_self_const_id,
query_specific_interface_id);
query_specific_interface);
}
auto LookupCppImpl(Context& context, SemIR::LocId loc_id,
SemIR::CoreInterface core_interface,
SemIR::ConstantId query_self_const_id,
SemIR::SpecificInterfaceId query_specific_interface_id,
SemIR::SpecificInterface query_specific_interface,
const TypeStructure* best_impl_type_structure,
SemIR::LocId best_impl_loc_id) -> SemIR::InstId {
// TODO: Infer a C++ type structure and check whether it's less strict than
@@ -575,11 +569,11 @@ auto LookupCppImpl(Context& context, SemIR::LocId loc_id,
return BuildCppUnaryOperatorWitness(context, loc_id, core_interface,
/*has_associated_result_type=*/false,
query_self_const_id,
query_specific_interface_id);
query_specific_interface);
case SemIR::CoreInterface::Negate:
return BuildCppUnaryOperatorWitness(
context, loc_id, core_interface, /*has_associated_result_type=*/true,
query_self_const_id, query_specific_interface_id);
query_self_const_id, query_specific_interface);
case SemIR::CoreInterface::AddWith:
case SemIR::CoreInterface::SubWith:
case SemIR::CoreInterface::MulWith:
@@ -588,7 +582,7 @@ auto LookupCppImpl(Context& context, SemIR::LocId loc_id,
return BuildCppBinaryOperatorWitness(context, loc_id, core_interface,
/*has_associated_result_type=*/true,
query_self_const_id,
query_specific_interface_id);
query_specific_interface);
case SemIR::CoreInterface::AddAssignWith:
case SemIR::CoreInterface::SubAssignWith:
case SemIR::CoreInterface::MulAssignWith:
@@ -597,34 +591,34 @@ auto LookupCppImpl(Context& context, SemIR::LocId loc_id,
return BuildCppBinaryOperatorWitness(context, loc_id, core_interface,
/*has_associated_result_type=*/false,
query_self_const_id,
query_specific_interface_id);
query_specific_interface);
case SemIR::CoreInterface::EqWith:
return BuildCppComparisonWitness(
context, loc_id, CoreIdentifier::EqWith,
{CoreIdentifier::Equal, CoreIdentifier::NotEqual},
query_self_const_id, query_specific_interface_id);
query_self_const_id, query_specific_interface);
case SemIR::CoreInterface::OrderedWith:
return BuildCppComparisonWitness(
context, loc_id, CoreIdentifier::OrderedWith,
{CoreIdentifier::Less, CoreIdentifier::LessOrEquivalent,
CoreIdentifier::Greater, CoreIdentifier::GreaterOrEquivalent},
query_self_const_id, query_specific_interface_id);
query_self_const_id, query_specific_interface);
case SemIR::CoreInterface::Copy:
return BuildCopyWitness(context, loc_id, query_self_const_id,
query_specific_interface_id);
query_specific_interface);
case SemIR::CoreInterface::CppUnsafeDeref:
return BuildCppUnsafeDerefWitness(context, loc_id, query_self_const_id,
query_specific_interface_id);
query_specific_interface);
case SemIR::CoreInterface::Default:
return BuildDefaultWitness(context, loc_id, query_self_const_id,
query_specific_interface_id);
query_specific_interface);
case SemIR::CoreInterface::Destroy:
return BuildDestroyWitness(context, loc_id, query_self_const_id,
query_specific_interface_id);
return BuildCppDestroyWitness(context, loc_id, query_self_const_id,
query_specific_interface);
case SemIR::CoreInterface::CppRangeForIterate:
return BuildCppRangeForIterateWitness(
context, loc_id, query_self_const_id, query_specific_interface_id);
context, loc_id, query_self_const_id, query_specific_interface);
// *FitsIn are implemented only by Carbon primitive types.
case SemIR::CoreInterface::IntFitsIn:
+1 -1
View File
@@ -40,7 +40,7 @@ namespace Carbon::Check {
auto LookupCppImpl(Context& context, SemIR::LocId loc_id,
SemIR::CoreInterface core_interface,
SemIR::ConstantId query_self_const_id,
SemIR::SpecificInterfaceId query_specific_interface_id,
SemIR::SpecificInterface query_specific_interface,
const TypeStructure* best_impl_type_structure,
SemIR::LocId best_impl_loc_id) -> SemIR::InstId;
+27 -34
View File
@@ -99,13 +99,28 @@ auto AddIdentifierName(Context& context, llvm::StringRef name)
}
// Adds a namespace for the `Cpp` import and returns its `NameScopeId`.
static auto AddNamespace(Context& context, PackageNameId cpp_package_id,
static auto AddNamespace(Context& context,
llvm::ArrayRef<Parse::Tree::PackagingNames> imports)
-> SemIR::NameScopeId {
if (imports.empty()) {
return AddImportNamespace(
context,
GetSingletonType(context, SemIR::NamespaceType::TypeInstId),
SemIR::NameId::Cpp, SemIR::NameScopeId::Package,
/*import_id=*/SemIR::InstId::None)
.name_scope_id;
}
PackageNameId package_id = imports.front().package_id;
CARBON_CHECK(
llvm::all_of(imports, [&](const Parse::Tree::PackagingNames& import) {
return import.package_id == package_id;
}));
return AddImportNamespaceToScope(
context,
GetSingletonType(context, SemIR::NamespaceType::TypeInstId),
SemIR::NameId::ForPackageName(cpp_package_id),
SemIR::NameId::ForPackageName(package_id),
SemIR::NameScopeId::Package,
/*diagnose_duplicate_namespace=*/false,
[&] {
@@ -121,19 +136,13 @@ static auto AddNamespace(Context& context, PackageNameId cpp_package_id,
auto ImportCpp(Context& context,
llvm::ArrayRef<Parse::Tree::PackagingNames> imports,
SemIR::CppDomain* domain) -> void {
if (imports.empty()) {
// TODO: Consider always having a (non-null) AST even if there are no Cpp
// imports.
// If there are no direct C++ imports and no shared domain covers this unit,
// there is nothing to import.
if (imports.empty() && !domain) {
return;
}
PackageNameId package_id = imports.front().package_id;
CARBON_CHECK(
llvm::all_of(imports, [&](const Parse::Tree::PackagingNames& import) {
return import.package_id == package_id;
}));
auto name_scope_id = AddNamespace(context, package_id, imports);
auto name_scope_id = AddNamespace(context, imports);
SemIR::NameScope& name_scope = context.name_scopes().Get(name_scope_id);
name_scope.set_is_closed_import(true);
@@ -353,28 +362,13 @@ auto ImportCppConstantFromFile(Context& context, SemIR::LocId loc_id,
return SemIR::ErrorInst::ConstantId;
}
auto const_inst_id = file.constant_values().GetConstantInstId(inst_id);
CARBON_KIND_SWITCH(file.insts().Get(const_inst_id)) {
case CARBON_KIND(SemIR::ClassType class_type): {
const auto& class_info = file.classes().Get(class_type.class_id);
CARBON_CHECK(class_info.scope_id.has_value());
return ImportCppDeclFromFile(
context, loc_id, file,
file.name_scopes().Get(class_info.scope_id).clang_decl_context_id());
}
case CARBON_KIND(SemIR::Namespace namespace_decl): {
return ImportCppDeclFromFile(context, loc_id, file,
file.name_scopes()
.Get(namespace_decl.name_scope_id)
.clang_decl_context_id());
}
default: {
context.TODO(loc_id, "indirect import of unsupported C++ declaration");
return SemIR::ErrorInst::ConstantId;
}
if (const auto* clang_decl = file.clang_decls().Lookup(inst_id)) {
auto clang_decl_id = file.clang_decls().LookupId(clang_decl->key);
return ImportCppDeclFromFile(context, loc_id, file, clang_decl_id);
}
context.TODO(loc_id, "indirect import of unsupported C++ declaration");
return SemIR::ErrorInst::ConstantId;
}
// Returns the Clang `DeclContext` for the given name scope. Return the
@@ -1973,7 +1967,6 @@ static auto ImportFunction(Context& context, SemIR::LocId loc_id,
.call_param_patterns_id =
function_params_insts->call_param_patterns_id,
.call_params_id = function_params_insts->call_params_id,
.call_param_default_values_id = SemIR::InstBlockId::None,
.call_param_ranges = function_params_insts->param_ranges,
.return_type_inst_id = function_params_insts->return_type_inst_id,
.return_form_inst_id = function_params_insts->return_form_inst_id,
+53 -3
View File
@@ -7,6 +7,7 @@
#include "clang/Sema/Initialization.h"
#include "clang/Sema/Overload.h"
#include "clang/Sema/Sema.h"
#include "toolchain/base/kind_switch.h"
#include "toolchain/check/convert.h"
#include "toolchain/check/core_identifier.h"
#include "toolchain/check/cpp/import.h"
@@ -16,6 +17,7 @@
#include "toolchain/check/custom_witness.h"
#include "toolchain/check/function.h"
#include "toolchain/check/inst.h"
#include "toolchain/check/name_lookup.h"
#include "toolchain/check/pattern.h"
#include "toolchain/check/type.h"
#include "toolchain/check/type_completion.h"
@@ -37,6 +39,8 @@ static auto GetClangOperatorKind(Context& context, SemIR::LocId loc_id,
switch (interface_name) {
// Unary operators.
case CoreIdentifier::Destroy:
case CoreIdentifier::SubobjectDestroy:
case CoreIdentifier::SelfDestruct:
case CoreIdentifier::As:
case CoreIdentifier::ImplicitAs:
case CoreIdentifier::Iterate:
@@ -491,6 +495,13 @@ namespace {
struct OverloadedOperatorInfo {
enum ReturnType { FirstArgType, Bool };
// The name of the interface containing the operator function. This affects
// the mangled name and canonicalization of Generated functions.
//
// This must always be set, so we pick a default value that does not represent
// an interface, so is never correct.
CoreIdentifier interface_name = CoreIdentifier::VoidBase;
// The name for the function used to implement this operator. This is usually
// `Op`. This mostly only affects the mangled name, but might show up in
// diagnostics.
@@ -517,49 +528,83 @@ static auto GetBuiltinOperatorInfo(clang::OverloadedOperatorKind kind)
// Bitwise operators. In C++, the return type is computed with the usual
// arithmetic conversions, but we will just use the type of the arguments.
table[clang::OO_Amp] = {
.interface_name = CoreIdentifier::BitAndWith,
.builtin_kind = SemIR::BuiltinFunctionKind::IntAnd,
.return_type = OverloadedOperatorInfo::ReturnType::FirstArgType};
table[clang::OO_Pipe] = {
.interface_name = CoreIdentifier::BitOrWith,
.builtin_kind = SemIR::BuiltinFunctionKind::IntOr,
.return_type = OverloadedOperatorInfo::ReturnType::FirstArgType};
table[clang::OO_Caret] = {
.interface_name = CoreIdentifier::BitXorWith,
.builtin_kind = SemIR::BuiltinFunctionKind::IntXor,
.return_type = OverloadedOperatorInfo::ReturnType::FirstArgType};
table[clang::OO_Tilde] = {
.interface_name = CoreIdentifier::BitComplement,
.builtin_kind = SemIR::BuiltinFunctionKind::IntComplement,
.return_type = OverloadedOperatorInfo::ReturnType::FirstArgType};
// Comparison operators.
table[clang::OO_EqualEqual] = {
.interface_name = CoreIdentifier::OrderedWith,
.op_name = CoreIdentifier::Equal,
.builtin_kind = SemIR::BuiltinFunctionKind::IntEq,
.return_type = OverloadedOperatorInfo::ReturnType::Bool};
table[clang::OO_ExclaimEqual] = {
.interface_name = CoreIdentifier::OrderedWith,
.op_name = CoreIdentifier::NotEqual,
.builtin_kind = SemIR::BuiltinFunctionKind::IntNeq,
.return_type = OverloadedOperatorInfo::ReturnType::Bool};
table[clang::OO_Less] = {
.interface_name = CoreIdentifier::OrderedWith,
.op_name = CoreIdentifier::Less,
.builtin_kind = SemIR::BuiltinFunctionKind::IntLess,
.return_type = OverloadedOperatorInfo::ReturnType::Bool};
table[clang::OO_LessEqual] = {
.interface_name = CoreIdentifier::OrderedWith,
.op_name = CoreIdentifier::LessOrEquivalent,
.builtin_kind = SemIR::BuiltinFunctionKind::IntLessEq,
.return_type = OverloadedOperatorInfo::ReturnType::Bool};
table[clang::OO_Greater] = {
.interface_name = CoreIdentifier::OrderedWith,
.op_name = CoreIdentifier::Greater,
.builtin_kind = SemIR::BuiltinFunctionKind::IntGreater,
.return_type = OverloadedOperatorInfo::ReturnType::Bool};
table[clang::OO_GreaterEqual] = {
.interface_name = CoreIdentifier::OrderedWith,
.op_name = CoreIdentifier::GreaterOrEquivalent,
.builtin_kind = SemIR::BuiltinFunctionKind::IntGreaterEq,
.return_type = OverloadedOperatorInfo::ReturnType::Bool};
return table;
}();
return OpTable[kind];
}
static auto GetCoreInterfaceId(Context& context, SemIR::LocId loc_id,
CoreIdentifier interface_name)
-> SemIR::InterfaceId {
auto inst_id = LookupNameInCore(context, loc_id, interface_name);
// Non-generic interfaces.
if (auto facet_type = context.insts().TryGetAs<SemIR::FacetType>(inst_id)) {
const auto& declared =
context.declared_facet_types().Get(facet_type->declared_facet_type_id);
auto single = declared.TryAsSingleExtend();
CARBON_KIND_SWITCH(*single) {
case CARBON_KIND(SemIR::SpecificInterface si): {
return si.interface_id;
}
case CARBON_KIND(SemIR::SpecificNamedConstraint _): {
CARBON_FATAL("Operators in named constraints are not yet needed");
}
}
}
auto type_id = context.insts().Get(inst_id).type_id();
auto generic = context.types().GetAs<SemIR::GenericInterfaceType>(type_id);
return generic.interface_id;
}
// Builds a Carbon builtin function declaration corresponding to an overload
// candidate that selected a C++ builtin operator. Returns None if no
// corresponding builtin function could or should be built.
@@ -572,6 +617,9 @@ static auto TryBuildBuiltinOperator(
return SemIR::InstId::None;
}
CARBON_CHECK(info.interface_name != CoreIdentifier::VoidBase,
"builtin operator interface was not specified");
// Import the argument types. For now, we only accept enum types.
// TODO: Consider expanding this to other types.
llvm::SmallVector<SemIR::TypeId, 2> arg_type_ids;
@@ -617,8 +665,10 @@ static auto TryBuildBuiltinOperator(
break;
}
return MakeBuiltinOperatorFunction(context, arg_type_ids, return_type_id,
info.op_name, info.builtin_kind);
return MakeBuiltinOperatorFunction(
context, loc_id, arg_type_ids, return_type_id, info.op_name,
info.builtin_kind,
GetCoreInterfaceId(context, loc_id, info.interface_name));
}
namespace {
+1 -1
View File
@@ -122,7 +122,7 @@ auto CheckCppOverloadAccess(
auto name_scope_const_id = context.constant_values().Get(
context.name_scopes().Get(parent_scope_id).inst_id());
SemIR::AccessKind allowed_access_kind =
GetHighestAllowedAccess(context, loc_id, name_scope_const_id);
GetHighestAllowedAccess(context, name_scope_const_id);
CheckAccess(context, loc_id, SemIR::LocId(overload_inst_id), function.name_id,
member_access_kind,
/*is_parent_access=*/false,
+323 -157
View File
@@ -6,6 +6,7 @@
#include "llvm/ADT/APFloat.h"
#include "toolchain/base/kind_switch.h"
#include "toolchain/check/call.h"
#include "toolchain/check/convert.h"
#include "toolchain/check/eval.h"
#include "toolchain/check/facet_type.h"
@@ -15,64 +16,117 @@
#include "toolchain/check/impl_lookup.h"
#include "toolchain/check/import_ref.h"
#include "toolchain/check/inst.h"
#include "toolchain/check/member_access.h"
#include "toolchain/check/name_lookup.h"
#include "toolchain/check/operator.h"
#include "toolchain/check/return.h"
#include "toolchain/check/type.h"
#include "toolchain/check/type_completion.h"
#include "toolchain/diagnostics/format_providers.h"
#include "toolchain/sem_ir/associated_constant.h"
#include "toolchain/sem_ir/builtin_function_kind.h"
#include "toolchain/sem_ir/constant.h"
#include "toolchain/sem_ir/function.h"
#include "toolchain/sem_ir/generic.h"
#include "toolchain/sem_ir/ids.h"
#include "toolchain/sem_ir/type_info.h"
#include "toolchain/sem_ir/typed_insts.h"
namespace Carbon::Check {
// Given a value whose type `IsFacetTypeOrError`, returns the corresponding
// type.
static auto GetFacetAsType(Context& context,
SemIR::ConstantId facet_or_type_const_id)
-> SemIR::TypeId {
auto facet_or_type_id =
context.constant_values().GetInstId(facet_or_type_const_id);
auto type_type_id = context.insts().Get(facet_or_type_id).type_id();
CARBON_CHECK(context.types().IsFacetTypeOrError(type_type_id));
// Make the CanonicalKey for a generated function `op_name_id` in the interface
// `core_specific_interface`.
static auto MakeGeneratedFunctionKey(
Context& context, SemIR::SpecificInterface core_specific_interface,
SemIR::TypeId self_type_id, SemIR::NameId op_name_id)
-> SemIR::GeneratedFunction::CanonicalKey {
// TODO: We'd like to build an Interface-with-Self specific here for the key,
// via MakeSpecificWithInnerSelf. But we are unable to make a facet value for
// Self with GetConstantFacetValueForTypeAndInterface() as we have no witness
// for the interface, because we don't have a CustomWitness instruction yet.
// To do so, we need to move the witness table out of the CustomWitness
// instruction, so that we can reorder things. Then we can make the
// CustomWitness inst first, and mutate the table as we build up the entries
// for it. For now, we use the InterfaceId and Interface-without-Self
// specific, and store the self TypeId separately instead.
auto specific_interface_id =
context.specific_interfaces().Add(core_specific_interface);
if (context.types().Is<SemIR::FacetType>(type_type_id)) {
// It's a facet; access its type.
facet_or_type_id = context.types().GetTypeInstId(
GetFacetAccessType(context, facet_or_type_id));
return SemIR::GeneratedFunction::CanonicalKey{specific_interface_id,
self_type_id, op_name_id};
}
// Attempts to return the canonical Function for a generated function.
//
// On success, returns the Decl and Function IDs of the canonical Function.
// Otherwise, it returns None for those IDs.
static auto TryGetGeneratedFunction(Context& context,
SemIR::GeneratedFunction::CanonicalKey key)
-> std::pair<SemIR::InstId, SemIR::FunctionId> {
if (auto generated_id = context.generated_functions().Lookup(key);
generated_id.has_value()) {
const auto& generated = context.generated_functions().Get(generated_id);
return {generated.decl_id, generated.function_id};
}
return context.types().GetTypeIdForTypeInstId(facet_or_type_id);
return {SemIR::InstId::None, SemIR::FunctionId::None};
}
static auto MakeCoreSpecificInterface(
Context& context, SemIR::LocId loc_id, SemIR::InterfaceId interface_id,
SemIR::GenericId interface_generic_id,
llvm::ArrayRef<SemIR::TypeId> param_types_without_self)
-> SemIR::SpecificInterface {
llvm::SmallVector<SemIR::InstId> params_without_self(
llvm::map_range(param_types_without_self, [&](SemIR::TypeId type_id) {
return context.types().GetTypeInstId(type_id);
}));
CARBON_CHECK(!params_without_self.empty() ==
interface_generic_id.has_value());
auto specific_id = SemIR::SpecificId::None;
if (!params_without_self.empty()) {
specific_id = MakeSpecific(context, loc_id, interface_generic_id,
params_without_self);
}
return {interface_id, specific_id};
}
// Returns a manufactured operator function.
auto MakeBuiltinOperatorFunction(Context& context,
auto MakeBuiltinOperatorFunction(Context& context, SemIR::LocId loc_id,
llvm::ArrayRef<SemIR::TypeId> param_types,
SemIR::TypeId return_type_id,
CoreIdentifier op_name,
SemIR::BuiltinFunctionKind builtin_kind,
SemIR::NameScopeId parent_scope_id)
SemIR::InterfaceId interface_id)
-> SemIR::InstId {
CARBON_CHECK(!param_types.empty());
auto self_type_id = param_types.front();
auto self_type_id = param_types.consume_front();
auto name_id = context.core_identifiers().AddNameId(op_name);
llvm::SmallVector<ParamPatternKind> param_kinds(param_types.size() - 1,
ParamPatternKind::Value);
auto [decl_id, function_id] = MakeGeneratedFunctionDecl(
context, SemIR::LocId::None,
{.parent_scope_id = parent_scope_id,
.name_id = name_id,
.self_type_id = self_type_id,
.self_kind = ParamPatternKind::Value,
.param_type_ids = param_types.drop_front(),
.param_kinds = param_kinds,
.return_form =
ReturnExprAsForm(context, SemIR::LocId::None,
context.types().GetTypeInstId(return_type_id))});
auto& function = context.functions().Get(function_id);
function.SetCoreWitness(builtin_kind);
const auto& interface = context.interfaces().Get(interface_id);
auto specific_interface = MakeCoreSpecificInterface(
context, loc_id, interface_id, interface.generic_id, param_types);
auto canonical_key = MakeGeneratedFunctionKey(context, specific_interface,
self_type_id, name_id);
auto [decl_id, function_id] = TryGetGeneratedFunction(context, canonical_key);
if (!decl_id.has_value()) {
llvm::SmallVector<ParamPatternKind> param_kinds(param_types.size(),
ParamPatternKind::Value);
std::tie(decl_id, function_id) = MakeGeneratedFunctionDecl(
context, SemIR::LocId::None,
{.parent_scope_id = interface.scope_with_self_id,
.name_id = name_id,
.self_type_id = self_type_id,
.self_kind = ParamPatternKind::Value,
.param_type_ids = param_types,
.param_kinds = param_kinds,
.return_form =
ReturnExprAsForm(context, SemIR::LocId::None,
context.types().GetTypeInstId(return_type_id))});
auto& function = context.functions().Get(function_id);
function.SetGenerated(context.generated_functions().Add(
{.canonical_key = canonical_key,
.function_id = function_id,
.decl_id = decl_id,
.builtin_function_kind = builtin_kind}));
}
return decl_id;
}
@@ -80,11 +134,7 @@ auto MakeBuiltinOperatorFunction(Context& context,
// Returns a FacetType that contains only the query interface.
static auto GetFacetTypeForQuerySpecificInterface(
Context& context, SemIR::LocId loc_id,
SemIR::SpecificInterfaceId query_specific_interface_id)
-> SemIR::ConstantId {
const auto query_specific_interface =
context.specific_interfaces().Get(query_specific_interface_id);
SemIR::SpecificInterface query_specific_interface) -> SemIR::ConstantId {
// The Self facet will have type FacetType, for the query interface.
auto const_id = EvalOrAddInst<SemIR::FacetType>(
context, loc_id,
@@ -97,13 +147,12 @@ static auto GetFacetTypeForQuerySpecificInterface(
// for lookups in `HasWitnessForRepeatedField`.
static auto PrepareForHasWitness(
Context& context, SemIR::LocId loc_id,
SemIR::SpecificInterfaceId query_specific_interface_id)
-> SemIR::ConstantId {
SemIR::SpecificInterface query_specific_interface) -> SemIR::ConstantId {
context.inst_block_stack().Push();
StartGenericDecl(context);
return GetFacetTypeForQuerySpecificInterface(context, loc_id,
query_specific_interface_id);
query_specific_interface);
}
// Cleans up state `PrepareForHasWitness`.
@@ -134,9 +183,9 @@ enum class DestroyFormat {
// field, this can handle the call to `PrepareForHasWitness`.
static auto HasWitnessForOneField(
Context& context, SemIR::LocId loc_id, SemIR::InstId field_inst_id,
SemIR::SpecificInterfaceId query_specific_interface_id) -> DestroyFormat {
SemIR::SpecificInterface query_specific_interface) -> DestroyFormat {
auto query_facet_type_const_id =
PrepareForHasWitness(context, loc_id, query_specific_interface_id);
PrepareForHasWitness(context, loc_id, query_specific_interface);
auto has_witness = HasWitnessForRepeatedField(context, loc_id, field_inst_id,
query_facet_type_const_id);
CleanupAfterHasWitness(context);
@@ -144,11 +193,11 @@ static auto HasWitnessForOneField(
}
// Returns true if `class_type` should impl `Destroy`.
static auto CanDestroyClass(
Context& context, SemIR::LocId loc_id, SemIR::ClassType class_type,
const SemIR::CompleteTypeInfo& complete_info,
SemIR::SpecificInterfaceId query_specific_interface_id, bool is_partial)
-> DestroyFormat {
static auto CanDestroyClass(Context& context, SemIR::LocId loc_id,
SemIR::ClassType class_type,
const SemIR::CompleteTypeInfo& complete_info,
SemIR::SpecificInterface query_specific_interface,
bool is_partial) -> DestroyFormat {
// Abstract classes can't be destroyed.
if (!is_partial && complete_info.IsAbstract()) {
return DestroyFormat::NoDestroy;
@@ -170,24 +219,24 @@ static auto CanDestroyClass(
return HasWitnessForOneField(context, loc_id,
context.types().GetTypeInstId(object_repr_id),
query_specific_interface_id);
query_specific_interface);
}
// Returns true if the `Self` should impl `Destroy`. This will recurse into impl
// lookup of `Destroy` for members, similar to `where .Self.members each impls
// Destroy`.
static auto CanDestroyType(
Context& context, SemIR::LocId loc_id,
SemIR::ConstantId query_self_const_id,
SemIR::SpecificInterfaceId query_specific_interface_id) -> DestroyFormat {
static auto CanDestroyType(Context& context, SemIR::LocId loc_id,
SemIR::ConstantId query_self_const_id,
SemIR::SpecificInterface query_specific_interface)
-> DestroyFormat {
auto inst_id = context.constant_values().GetInstId(
GetCanonicalFacetOrTypeValue(context, query_self_const_id));
GetCanonicalFacet(context, query_self_const_id));
auto inst = context.insts().Get(inst_id);
if (context.types().Is<SemIR::FacetType>(inst.type_id())) {
// The value's type is a facet (whose type is a facet type). We don't
// provide a custom witness for symbolic values of type facet. The witness
// will be found from impl lookup.
if (context.types().IsConstrainedFacetType(inst.type_id())) {
// The value's type is a symbolic constrained facet. We don't provide a
// custom witness for constrained facets. The witness must be found in the
// constraints by impl lookup.
CARBON_CHECK(query_self_const_id.is_symbolic());
return DestroyFormat::NoDestroy;
}
@@ -217,7 +266,7 @@ static auto CanDestroyType(
// Verify the element can be destroyed.
return HasWitnessForOneField(context, loc_id,
array_type.element_type_inst_id,
query_specific_interface_id);
query_specific_interface);
}
case SemIR::Call::Kind:
@@ -228,19 +277,19 @@ static auto CanDestroyType(
case CARBON_KIND(SemIR::ClassType class_type): {
return CanDestroyClass(context, loc_id, class_type,
context.types().GetCompleteTypeInfo(type_id),
query_specific_interface_id,
query_specific_interface,
/*is_partial=*/false);
}
case CARBON_KIND(SemIR::ConstType const_type): {
return HasWitnessForOneField(context, loc_id, const_type.inner_id,
query_specific_interface_id);
query_specific_interface);
}
case CARBON_KIND(SemIR::MaybeUnformedType maybe_unformed_type): {
return HasWitnessForOneField(context, loc_id,
maybe_unformed_type.inner_id,
query_specific_interface_id);
query_specific_interface);
}
case CARBON_KIND(SemIR::PartialType partial_type): {
@@ -250,7 +299,7 @@ static auto CanDestroyType(
context.insts().GetAs<SemIR::ClassType>(partial_type.inner_id);
return CanDestroyClass(context, loc_id, class_type,
context.types().GetCompleteTypeInfo(type_id),
query_specific_interface_id,
query_specific_interface,
/*is_partial=*/true);
}
@@ -260,7 +309,7 @@ static auto CanDestroyType(
return DestroyFormat::Trivial;
}
auto query_facet_type_const_id =
PrepareForHasWitness(context, loc_id, query_specific_interface_id);
PrepareForHasWitness(context, loc_id, query_specific_interface);
bool has_witness = true;
for (const auto& field : fields) {
if (!HasWitnessForRepeatedField(context, loc_id, field.type_inst_id,
@@ -279,7 +328,7 @@ static auto CanDestroyType(
return DestroyFormat::Trivial;
}
auto query_facet_type_const_id =
PrepareForHasWitness(context, loc_id, query_specific_interface_id);
PrepareForHasWitness(context, loc_id, query_specific_interface);
bool has_witness = true;
for (const auto& element_id : block) {
if (!HasWitnessForRepeatedField(context, loc_id, element_id,
@@ -299,7 +348,6 @@ static auto CanDestroyType(
case SemIR::IntLiteralType::Kind:
case SemIR::IntType::Kind:
case SemIR::PointerType::Kind:
case SemIR::TypeType::Kind:
// Trivially destructible.
return DestroyFormat::Trivial;
@@ -308,14 +356,14 @@ static auto CanDestroyType(
}
}
// Returns the body for `Destroy.Op`.
// Returns the body for `SubobjectDestroy.Op`.
//
// TODO: This is a placeholder still not actually destroying things, intended to
// maintain mostly-consistent behavior with current logic while working. That
// also means using `self`.
static auto MakeDestroyOpBody(Context& context, SemIR::LocId loc_id,
SemIR::TypeId self_type_id,
SemIR::InstId self_param_id)
static auto MakeSubobjectDestroyOpBody(Context& context, SemIR::LocId loc_id,
SemIR::TypeId self_type_id,
SemIR::InstId self_param_id)
-> SemIR::InstBlockId {
context.inst_block_stack().Push();
auto inst = context.types().GetAsInst(self_type_id);
@@ -332,7 +380,7 @@ static auto MakeDestroyOpBody(Context& context, SemIR::LocId loc_id,
// TODO: Implement destruction of the type.
break;
default:
CARBON_FATAL("Unexpected type for MakeDestroyOpBody: {0}", inst);
CARBON_FATAL("Unexpected type for MakeSubobjectDestroyOpBody: {0}", inst);
}
AddInst(context, loc_id, SemIR::Return{});
@@ -343,35 +391,133 @@ static auto MakeDestroyOpBody(Context& context, SemIR::LocId loc_id,
// to `self_type_id`.
static auto MakeDestroyOpFunction(Context& context, SemIR::LocId loc_id,
SemIR::TypeId self_type_id,
SemIR::NameScopeId parent_scope_id,
DestroyFormat format) -> SemIR::InstId {
SemIR::InterfaceId interface_id)
-> SemIR::InstId {
auto name_id = context.core_identifiers().AddNameId(CoreIdentifier::Op);
auto [decl_id, function_id] =
MakeGeneratedFunctionDecl(context, loc_id,
{.parent_scope_id = parent_scope_id,
.name_id = name_id,
.self_type_id = self_type_id,
.self_kind = ParamPatternKind::Ref});
auto& function = context.functions().Get(function_id);
if (format == DestroyFormat::Trivial) {
function.SetCoreWitness(SemIR::BuiltinFunctionKind::NoOp);
} else {
CARBON_CHECK(format == DestroyFormat::NonTrivial);
function.SetCoreWitness(SemIR::BuiltinFunctionKind::None);
auto body_id = MakeDestroyOpBody(context, loc_id, self_type_id,
function.self_param_id);
function.body_block_ids.push_back(body_id);
const auto& interface = context.interfaces().Get(interface_id);
auto specific_interface = MakeCoreSpecificInterface(
context, loc_id, interface_id, interface.generic_id, {});
auto canonical_key = MakeGeneratedFunctionKey(context, specific_interface,
self_type_id, name_id);
auto [decl_id, function_id] = TryGetGeneratedFunction(context, canonical_key);
if (!decl_id.has_value()) {
std::tie(decl_id, function_id) = MakeGeneratedFunctionDecl(
context, loc_id,
{.parent_scope_id = interface.scope_with_self_id,
.name_id = name_id,
.self_type_id = self_type_id,
.self_kind = ParamPatternKind::Ref});
auto& function = context.functions().Get(function_id);
function.SetGenerated(context.generated_functions().Add(
{.canonical_key = canonical_key,
.function_id = function_id,
.decl_id = decl_id,
.builtin_function_kind = SemIR::BuiltinFunctionKind::NoOp}));
}
return decl_id;
}
static auto MakeSubobjectDestroyOpFunction(
Context& context, SemIR::LocId loc_id, SemIR::TypeId self_type_id,
SemIR::InterfaceId interface_id, DestroyFormat format) -> SemIR::InstId {
// TODO: replace with `CoreIdentifier::Op` when `require impls` adds a witness
// table entry.
auto name_id =
context.core_identifiers().AddNameId(CoreIdentifier::SubobjectDestroy);
const auto& interface = context.interfaces().Get(interface_id);
auto specific_interface = MakeCoreSpecificInterface(
context, loc_id, interface_id, interface.generic_id, {});
auto canonical_key = MakeGeneratedFunctionKey(context, specific_interface,
self_type_id, name_id);
auto [decl_id, function_id] = TryGetGeneratedFunction(context, canonical_key);
if (!decl_id.has_value()) {
std::tie(decl_id, function_id) = MakeGeneratedFunctionDecl(
context, loc_id,
{.parent_scope_id = interface.scope_with_self_id,
.name_id = name_id,
.self_type_id = self_type_id,
.self_kind = ParamPatternKind::Ref});
auto& function = context.functions().Get(function_id);
auto builtin_kind = SemIR::BuiltinFunctionKind::None;
switch (format) {
case DestroyFormat::Trivial:
builtin_kind = SemIR::BuiltinFunctionKind::NoOp;
break;
case DestroyFormat::NonTrivial: {
auto body_id = MakeSubobjectDestroyOpBody(context, loc_id, self_type_id,
function.self_param_id);
function.body_block_ids.push_back(body_id);
break;
}
case DestroyFormat::NoDestroy:
CARBON_FATAL("unexpected DestroyFormat::NoDestroy");
}
function.SetGenerated(context.generated_functions().Add(
{.canonical_key = canonical_key,
.function_id = function_id,
.decl_id = decl_id,
.builtin_function_kind = builtin_kind}));
}
return decl_id;
}
// Returns a manufactured `Destroy.SelfDestruct` function with the `self`
// parameter typed to `self_type_id`.
static auto MakeDestroySelfDestructFunction(
Context& context, SemIR::LocId loc_id, SemIR::TypeId self_type_id,
SemIR::InterfaceId interface_id, SemIR::InstId op_id,
[[maybe_unused]] SemIR::InstId subobject_destroy_id) -> SemIR::InstId {
auto name_id =
context.core_identifiers().AddNameId(CoreIdentifier::SelfDestruct);
const auto& interface = context.interfaces().Get(interface_id);
auto specific_interface = MakeCoreSpecificInterface(
context, loc_id, interface_id, interface.generic_id, {});
auto canonical_key = MakeGeneratedFunctionKey(context, specific_interface,
self_type_id, name_id);
auto [decl_id, function_id] = TryGetGeneratedFunction(context, canonical_key);
if (!decl_id.has_value()) {
std::tie(decl_id, function_id) = MakeGeneratedFunctionDecl(
context, loc_id,
{.parent_scope_id = interface.scope_with_self_id,
.name_id = name_id,
.self_type_id = self_type_id,
.self_kind = ParamPatternKind::Ref});
auto& function = context.functions().Get(function_id);
context.inst_block_stack().Push();
StartFunctionDefinition(context, decl_id, function_id);
auto params = context.inst_blocks().Get(function.call_params_id);
CARBON_CHECK(
params.size() == 1,
"`Core.Destroy.SelfDestruct` should only have `ref self` as its "
"parameter");
op_id = PerformCall(context, loc_id, op_id, {params[0]}, true);
DiscardExpr(context, op_id);
subobject_destroy_id =
PerformCall(context, loc_id, subobject_destroy_id, {params[0]}, true);
DiscardExpr(context, subobject_destroy_id);
BuildReturnWithNoExpr(context, loc_id);
FinishFunctionDefinition(context, function_id);
context.inst_block_stack().Pop();
function.SetGenerated(context.generated_functions().Add(
{.canonical_key = canonical_key,
.function_id = function_id,
.decl_id = decl_id,
.builtin_function_kind = SemIR::BuiltinFunctionKind::None}));
}
return decl_id;
}
static auto MakeCustomWitnessConstantInst(
Context& context, SemIR::LocId loc_id,
SemIR::SpecificInterfaceId query_specific_interface_id,
SemIR::SpecificInterface query_specific_interface,
SemIR::InstBlockId associated_entities_block_id) -> SemIR::InstId {
// The witness is a CustomWitness of the query interface with a table that
// contains each associated entity.
@@ -379,7 +525,8 @@ static auto MakeCustomWitnessConstantInst(
context, loc_id,
{.type_id = GetSingletonType(context, SemIR::WitnessType::TypeInstId),
.elements_id = associated_entities_block_id,
.query_specific_interface_id = query_specific_interface_id});
.query_specific_interface_id =
context.specific_interfaces().Add(query_specific_interface)});
return context.constant_values().GetInstId(const_id);
}
@@ -393,16 +540,16 @@ struct TypesForSelfFacet {
static auto GetTypesForSelfFacet(
Context& context, SemIR::LocId loc_id,
SemIR::ConstantId query_self_const_id,
SemIR::SpecificInterfaceId query_specific_interface_id)
-> TypesForSelfFacet {
SemIR::SpecificInterface query_specific_interface) -> TypesForSelfFacet {
// The Self facet will have type FacetType, for the query interface.
auto facet_type_for_query_specific_interface =
context.types().GetTypeIdForTypeConstantId(
GetFacetTypeForQuerySpecificInterface(context, loc_id,
query_specific_interface_id));
query_specific_interface));
// The Self facet needs to point to a type value. If it's not one already,
// convert to type.
auto query_self_as_type_id = GetFacetAsType(context, query_self_const_id);
auto query_self_as_type_id = GetFacetAccessType(
context, context.constant_values().GetInstId(query_self_const_id));
return {facet_type_for_query_specific_interface, query_self_as_type_id};
}
@@ -410,14 +557,13 @@ static auto GetTypesForSelfFacet(
// interface with an entry for each associated entity so far.
static auto MakeSelfFacetWithCustomWitness(
Context& context, SemIR::LocId loc_id, TypesForSelfFacet query_types,
SemIR::SpecificInterfaceId query_specific_interface_id,
SemIR::SpecificInterface query_specific_interface,
SemIR::InstBlockId associated_entities_block_id) -> SemIR::ConstantId {
// We are building a facet value for a single interface, so the witness block
// is a single witness for that interface.
auto witnesses_block_id =
context.inst_blocks().Add({MakeCustomWitnessConstantInst(
context, loc_id, query_specific_interface_id,
associated_entities_block_id)});
auto witnesses_block_id = context.inst_blocks().Add(
{MakeCustomWitnessConstantInst(context, loc_id, query_specific_interface,
associated_entities_block_id)});
return EvalOrAddInst<SemIR::FacetValue>(
context, loc_id,
@@ -429,10 +575,8 @@ static auto MakeSelfFacetWithCustomWitness(
auto BuildCustomWitness(Context& context, SemIR::LocId loc_id,
SemIR::ConstantId query_self_const_id,
SemIR::SpecificInterfaceId query_specific_interface_id,
SemIR::SpecificInterface query_specific_interface,
llvm::ArrayRef<SemIR::InstId> values) -> SemIR::InstId {
const auto query_specific_interface =
context.specific_interfaces().Get(query_specific_interface_id);
const auto& interface =
context.interfaces().Get(query_specific_interface.interface_id);
auto assoc_entities =
@@ -445,7 +589,7 @@ auto BuildCustomWitness(Context& context, SemIR::LocId loc_id,
}
auto query_types_for_self_facet = GetTypesForSelfFacet(
context, loc_id, query_self_const_id, query_specific_interface_id);
context, loc_id, query_self_const_id, query_specific_interface);
// The values that will go in the witness table.
llvm::SmallVector<SemIR::InstId> entries;
@@ -486,7 +630,7 @@ auto BuildCustomWitness(Context& context, SemIR::LocId loc_id,
if (associated_entity_state < new_associated_entity_state) {
auto self_facet = MakeSelfFacetWithCustomWitness(
context, loc_id, query_types_for_self_facet,
query_specific_interface_id, context.inst_blocks().Add(entries));
query_specific_interface, context.inst_blocks().Add(entries));
interface_with_self_specific_id = MakeSpecificWithInnerSelf(
context, loc_id, interface.generic_id,
interface.generic_with_self_id,
@@ -555,7 +699,7 @@ auto BuildCustomWitness(Context& context, SemIR::LocId loc_id,
}
return MakeCustomWitnessConstantInst(context, loc_id,
query_specific_interface_id,
query_specific_interface,
context.inst_blocks().Add(entries));
}
@@ -584,39 +728,63 @@ auto GetCoreInterface(Context& context, SemIR::InterfaceId interface_id)
}
auto BuildPrimitiveCopyWitness(
Context& context, SemIR::LocId loc_id, SemIR::NameScopeId parent_scope_id,
Context& context, SemIR::LocId loc_id,
SemIR::ConstantId query_self_const_id,
SemIR::SpecificInterfaceId query_specific_interface_id) -> SemIR::InstId {
auto self_type_id = GetFacetAsType(context, query_self_const_id);
SemIR::SpecificInterface query_specific_interface) -> SemIR::InstId {
auto self_type_id = GetFacetAccessType(
context, context.constant_values().GetInstId(query_self_const_id));
auto op_id = MakeBuiltinOperatorFunction(
context, {self_type_id}, self_type_id, CoreIdentifier::Op,
SemIR::BuiltinFunctionKind::PrimitiveCopy, parent_scope_id);
context, loc_id, {self_type_id}, self_type_id, CoreIdentifier::Op,
SemIR::BuiltinFunctionKind::PrimitiveCopy,
query_specific_interface.interface_id);
return BuildCustomWitness(context, loc_id, query_self_const_id,
query_specific_interface_id, {op_id});
query_specific_interface, {op_id});
}
auto BuildDestroyWitness(Context& context, SemIR::LocId loc_id,
SemIR::TypeId self_type_id,
SemIR::ConstantId query_self_const_id,
SemIR::SpecificInterface query_specific_interface,
SemIR::InstId subobject_destroy_fn_id)
-> SemIR::InstId {
auto interface =
context.interfaces().Get(query_specific_interface.interface_id);
auto assoc_entities =
context.inst_blocks().Get(interface.associated_entities_id);
CARBON_CHECK(assoc_entities.size() == 3,
"{} only has {} associated functions",
context.names().GetAsStringIfIdentifier(interface.name_id),
assoc_entities.size());
auto op_id = MakeDestroyOpFunction(context, loc_id, self_type_id,
query_specific_interface.interface_id);
auto self_destruct_fn_id = MakeDestroySelfDestructFunction(
context, loc_id, self_type_id, query_specific_interface.interface_id,
op_id, subobject_destroy_fn_id);
return BuildCustomWitness(
context, loc_id, query_self_const_id, query_specific_interface,
{op_id, subobject_destroy_fn_id, self_destruct_fn_id});
}
// Builds and returns a custom witness that performs the specified kind of
// destruction for the given type.
static auto BuildDestroyWitness(
static auto BuildCarbonDestroyWitness(
Context& context, SemIR::LocId loc_id,
SemIR::ConstantId query_self_const_id,
SemIR::SpecificInterfaceId query_specific_interface_id,
DestroyFormat format) -> SemIR::InstId {
SemIR::SpecificInterface query_specific_interface, DestroyFormat format)
-> SemIR::InstId {
CARBON_CHECK(format != DestroyFormat::NoDestroy);
// Mark functions with the interface's scope as a hint to mangling. This
// does not add them to the scope.
auto query_specific_interface =
context.specific_interfaces().Get(query_specific_interface_id);
auto parent_scope_id = context.interfaces()
.Get(query_specific_interface.interface_id)
.scope_without_self_id;
auto self_type_id = GetFacetAsType(context, query_self_const_id);
auto op_id = MakeDestroyOpFunction(context, loc_id, self_type_id,
parent_scope_id, format);
return BuildCustomWitness(context, loc_id, query_self_const_id,
query_specific_interface_id, {op_id});
auto self_type_id = GetFacetAccessType(
context, context.constant_values().GetInstId(query_self_const_id));
auto subobject_destroy_op_id = MakeSubobjectDestroyOpFunction(
context, loc_id, self_type_id, query_specific_interface.interface_id,
format);
return BuildDestroyWitness(context, loc_id, self_type_id, query_self_const_id,
query_specific_interface, subobject_destroy_op_id);
}
// Returns the custom witness to use for destruction of the given type. See
@@ -624,10 +792,10 @@ static auto BuildDestroyWitness(
static auto LookupDestroyWitness(
Context& context, SemIR::LocId loc_id,
SemIR::ConstantId query_self_const_id,
SemIR::SpecificInterfaceId query_specific_interface_id, bool build_witness)
SemIR::SpecificInterface query_specific_interface, bool build_witness)
-> std::optional<SemIR::InstId> {
auto format = CanDestroyType(context, loc_id, query_self_const_id,
query_specific_interface_id);
query_specific_interface);
if (format == DestroyFormat::NoDestroy) {
return std::nullopt;
}
@@ -637,27 +805,24 @@ static auto LookupDestroyWitness(
return SemIR::InstId::None;
}
return BuildDestroyWitness(context, loc_id, query_self_const_id,
query_specific_interface_id, format);
return BuildCarbonDestroyWitness(context, loc_id, query_self_const_id,
query_specific_interface, format);
}
auto BuildTrivialDestroyWitness(
Context& context, SemIR::LocId loc_id,
SemIR::ConstantId query_self_const_id,
SemIR::SpecificInterfaceId query_specific_interface_id) -> SemIR::InstId {
return BuildDestroyWitness(context, loc_id, query_self_const_id,
query_specific_interface_id,
DestroyFormat::Trivial);
SemIR::SpecificInterface query_specific_interface) -> SemIR::InstId {
return BuildCarbonDestroyWitness(context, loc_id, query_self_const_id,
query_specific_interface,
DestroyFormat::Trivial);
}
static auto MakeIntFitsInWitness(
Context& context, SemIR::LocId loc_id,
SemIR::ConstantId query_self_const_id,
SemIR::SpecificInterfaceId query_specific_interface_id, bool build_witness)
SemIR::SpecificInterface query_specific_interface, bool build_witness)
-> std::optional<SemIR::InstId> {
auto query_specific_interface =
context.specific_interfaces().Get(query_specific_interface_id);
auto args_id = query_specific_interface.specific_id;
if (!args_id.has_value()) {
return std::nullopt;
@@ -673,8 +838,10 @@ static auto MakeIntFitsInWitness(
return std::nullopt;
}
auto src_type_id = GetFacetAsType(context, query_self_const_id);
auto dest_type_id = GetFacetAsType(context, dest_const_id);
auto src_type_id = GetFacetAccessType(
context, context.constant_values().GetInstId(query_self_const_id));
auto dest_type_id = GetFacetAccessType(
context, context.constant_values().GetInstId(dest_const_id));
auto context_fn = [](DiagnosticContextBuilder& /*builder*/) -> void {};
if (!RequireCompleteType(context, src_type_id, loc_id, context_fn) ||
@@ -709,7 +876,7 @@ static auto MakeIntFitsInWitness(
return SemIR::InstId::None;
}
return BuildCustomWitness(context, loc_id, query_self_const_id,
query_specific_interface_id, {});
query_specific_interface, {});
}
return std::nullopt;
}
@@ -746,17 +913,14 @@ static auto MakeIntFitsInWitness(
}
return BuildCustomWitness(context, loc_id, query_self_const_id,
query_specific_interface_id, {});
query_specific_interface, {});
}
static auto MakeFloatFitsInWitness(
Context& context, SemIR::LocId loc_id,
SemIR::ConstantId query_self_const_id,
SemIR::SpecificInterfaceId query_specific_interface_id, bool build_witness)
SemIR::SpecificInterface query_specific_interface, bool build_witness)
-> std::optional<SemIR::InstId> {
auto query_specific_interface =
context.specific_interfaces().Get(query_specific_interface_id);
auto args_id = query_specific_interface.specific_id;
if (!args_id.has_value()) {
return std::nullopt;
@@ -772,8 +936,10 @@ static auto MakeFloatFitsInWitness(
return std::nullopt;
}
auto src_type_id = GetFacetAsType(context, query_self_const_id);
auto dest_type_id = GetFacetAsType(context, dest_const_id);
auto src_type_id = GetFacetAccessType(
context, context.constant_values().GetInstId(query_self_const_id));
auto dest_type_id = GetFacetAccessType(
context, context.constant_values().GetInstId(dest_const_id));
auto context_fn = [](DiagnosticContextBuilder& /*builder*/) -> void {};
if (!RequireCompleteType(context, src_type_id, loc_id, context_fn) ||
@@ -818,24 +984,24 @@ static auto MakeFloatFitsInWitness(
}
return BuildCustomWitness(context, loc_id, query_self_const_id,
query_specific_interface_id, {});
query_specific_interface, {});
}
auto LookupCustomWitness(Context& context, SemIR::LocId loc_id,
SemIR::CoreInterface core_interface,
SemIR::ConstantId query_self_const_id,
SemIR::SpecificInterfaceId query_specific_interface_id,
SemIR::SpecificInterface query_specific_interface,
bool build_witness) -> std::optional<SemIR::InstId> {
switch (core_interface) {
case SemIR::CoreInterface::Destroy:
return LookupDestroyWitness(context, loc_id, query_self_const_id,
query_specific_interface_id, build_witness);
query_specific_interface, build_witness);
case SemIR::CoreInterface::FloatFitsIn:
return MakeFloatFitsInWitness(context, loc_id, query_self_const_id,
query_specific_interface_id, build_witness);
query_specific_interface, build_witness);
case SemIR::CoreInterface::IntFitsIn:
return MakeIntFitsInWitness(context, loc_id, query_self_const_id,
query_specific_interface_id, build_witness);
query_specific_interface, build_witness);
case SemIR::CoreInterface::AddAssignWith:
case SemIR::CoreInterface::AddWith:
case SemIR::CoreInterface::Copy:
+24 -10
View File
@@ -17,31 +17,34 @@ namespace Carbon::Check {
// values aren't suitable for the interface.
auto BuildCustomWitness(Context& context, SemIR::LocId loc_id,
SemIR::ConstantId query_self_const_id,
SemIR::SpecificInterfaceId query_specific_interface_id,
SemIR::SpecificInterface query_specific_interface,
llvm::ArrayRef<SemIR::InstId> values) -> SemIR::InstId;
// Builds a witness that the given type is copyable via a primitive copy.
auto BuildPrimitiveCopyWitness(
Context& context, SemIR::LocId loc_id, SemIR::NameScopeId parent_scope_id,
Context& context, SemIR::LocId loc_id,
SemIR::ConstantId query_self_const_id,
SemIR::SpecificInterfaceId query_specific_interface_id) -> SemIR::InstId;
SemIR::SpecificInterface query_specific_interface) -> SemIR::InstId;
// Returns a manufactured operator function.
// `param_types` contains the parameter types. The first element of
// `param_types` is treated as the `self` type, and any subsequent elements
// are treated as the types of the remaining explicit parameters.
auto MakeBuiltinOperatorFunction(
Context& context, llvm::ArrayRef<SemIR::TypeId> param_types,
SemIR::TypeId return_type_id, CoreIdentifier op_name,
SemIR::BuiltinFunctionKind builtin_kind,
SemIR::NameScopeId parent_scope_id = SemIR::NameScopeId::None)
// The `interface_id` is the interface containing the `op_name` function, which
// should be an interface in Core.
auto MakeBuiltinOperatorFunction(Context& context, SemIR::LocId loc_id,
llvm::ArrayRef<SemIR::TypeId> param_types,
SemIR::TypeId return_type_id,
CoreIdentifier op_name,
SemIR::BuiltinFunctionKind builtin_kind,
SemIR::InterfaceId interface_id)
-> SemIR::InstId;
// Builds a witness that the given type is trivially destroyable.
auto BuildTrivialDestroyWitness(
Context& context, SemIR::LocId loc_id,
SemIR::ConstantId query_self_const_id,
SemIR::SpecificInterfaceId query_specific_interface_id) -> SemIR::InstId;
SemIR::SpecificInterface query_specific_interface) -> SemIR::InstId;
// Given an interface, returns the corresponding enum if it's covered by
// `CoreInterface`, or `Unknown` if it's some other interface.
@@ -61,9 +64,20 @@ auto AsCoreIdentifier(SemIR::CoreInterface core_interface) -> CoreIdentifier;
auto LookupCustomWitness(Context& context, SemIR::LocId loc_id,
SemIR::CoreInterface core_interface,
SemIR::ConstantId query_self_const_id,
SemIR::SpecificInterfaceId query_specific_interface_id,
SemIR::SpecificInterface query_specific_interface,
bool build_witness) -> std::optional<SemIR::InstId>;
// Builds a witness for the `Destroy` interface.
//
// `op_id` refers to the synthesised `Destroy.Op` and is generated differently
// based on whether the specific is a Carbon type or a C++ type.
auto BuildDestroyWitness(Context& context, SemIR::LocId loc_id,
SemIR::TypeId self_type_id,
SemIR::ConstantId query_self_const_id,
SemIR::SpecificInterface query_specific_interface,
SemIR::InstId subobject_destroy_fn_id)
-> SemIR::InstId;
} // namespace Carbon::Check
#endif // CARBON_TOOLCHAIN_CHECK_CUSTOM_WITNESS_H_
+18
View File
@@ -13,6 +13,7 @@
#include "toolchain/check/merge.h"
#include "toolchain/check/name_component.h"
#include "toolchain/check/name_lookup.h"
#include "toolchain/check/type.h"
#include "toolchain/check/type_completion.h"
#include "toolchain/check/unused.h"
#include "toolchain/diagnostics/diagnostic.h"
@@ -65,6 +66,8 @@ auto DeclNameStack::PushScopeAndStartName() -> void {
// Create a scope for any parameters introduced in this name.
context_->scope_stack().PushForDeclName();
UpdateAccessContext();
}
auto DeclNameStack::FinishName(const NameComponent& name) -> NameContext {
@@ -93,6 +96,8 @@ auto DeclNameStack::PopScope(bool check_unused) -> void {
context_->scope_stack().PopTo(decl_name_stack_.back().initial_scope_index,
check_unused);
decl_name_stack_.pop_back();
UpdateAccessContext();
}
auto DeclNameStack::Suspend() -> SuspendedName {
@@ -108,6 +113,9 @@ auto DeclNameStack::Suspend() -> SuspendedName {
CARBON_CHECK(scope_stack.PeekIndex() == scope_index,
"Scope index {0} does not enclose the current scope {1}",
scope_index, scope_stack.PeekIndex());
UpdateAccessContext();
return result;
}
@@ -131,6 +139,8 @@ auto DeclNameStack::Restore(SuspendedName&& sus) -> void {
context_->scope_stack().Restore(std::move(suspended_scope));
}
UpdateAccessContext();
}
auto DeclNameStack::AddName(NameContext name_context, SemIR::InstId target_id,
@@ -523,4 +533,12 @@ auto DeclNameStack::ResolveAsScope(const NameContext& name_context,
}
}
auto DeclNameStack::UpdateAccessContext() const -> void {
if (decl_name_stack_.empty()) {
context_->access_context() = SemIR::NameScopeId::None;
} else {
context_->access_context() = decl_name_stack_.back().parent_scope_id;
}
}
} // namespace Carbon::Check
+3
View File
@@ -279,6 +279,9 @@ class DeclNameStack {
const NameComponent& name) const
-> std::pair<SemIR::NameScopeId, SemIR::GenericId>;
// Update `Context.access_context` to the current NameContext's parent scope.
auto UpdateAccessContext() const -> void;
// The linked context.
Context* context_;
+3 -3
View File
@@ -306,11 +306,11 @@ auto DeductionContext::Deduce() -> bool {
if (context().types().Is<SemIR::PatternType>(param_type_id)) {
param_type_id =
SemIR::ExtractScrutineeType(context().sem_ir(), param_type_id);
} else if (context().types().IsFacetType(param_type_id)) {
} else if (context().types().Is<SemIR::FacetType>(param_type_id)) {
// Given `fn F[G: Interface](g: G)`, the type of `g` is `G as type`. For
// deduction, we want to ignore the `as type`, and check that the argument
// can convert to the FacetType of the canonical facet value.
param_id = GetCanonicalFacetOrTypeValue(context(), param_id);
// can convert to the FacetType of the canonical facet.
param_id = GetCanonicalFacet(context(), param_id);
param = context().insts().Get(param_id);
param_type_id = param.type_id();
}
+1 -8
View File
@@ -92,14 +92,7 @@ auto DiagnosticEmitter::ConvertArg(llvm::Any arg) const -> llvm::Any {
if (!type_of_expr->inst_id.has_value()) {
return "<none>";
}
// TODO: Where possible, produce a better description of the type based on
// the expression.
return "`" +
StringifyConstantInst(
*sem_ir_,
sem_ir_->types().GetTypeInstId(
sem_ir_->insts().Get(type_of_expr->inst_id).type_id())) +
"`";
return "`" + StringifyTypeOfInst(*sem_ir_, type_of_expr->inst_id) + "`";
}
if (auto* expr = llvm::any_cast<InstIdAsConstant>(&arg)) {
return "`" + StringifyConstantInst(*sem_ir_, expr->inst_id) + "`";
+18 -6
View File
@@ -48,6 +48,12 @@ LLVM_DUMP_METHOD static auto Dump(const Context& context,
return SemIR::Dump(context.sem_ir(), bundle_id);
}
LLVM_DUMP_METHOD static auto Dump(
const Context& context, SemIR::GeneratedFunctionId generated_function_id)
-> std::string {
return SemIR::Dump(context.sem_ir(), generated_function_id);
}
LLVM_DUMP_METHOD static auto Dump(const Context& context,
SemIR::ClassId class_id) -> std::string {
return SemIR::Dump(context.sem_ir(), class_id);
@@ -58,18 +64,24 @@ LLVM_DUMP_METHOD static auto Dump(const Context& context,
return SemIR::Dump(context.sem_ir(), const_id);
}
LLVM_DUMP_METHOD static auto Dump(const Context& context,
SemIR::EntityNameId entity_name_id)
-> std::string {
return SemIR::Dump(context.sem_ir(), entity_name_id);
}
LLVM_DUMP_METHOD static auto Dump(
const Context& context, SemIR::DeclaredFacetTypeId declared_facet_type_id)
-> std::string {
return SemIR::Dump(context.sem_ir(), declared_facet_type_id);
}
LLVM_DUMP_METHOD static auto Dump(const Context& context,
SemIR::DefaultValueId value_id)
-> std::string {
return SemIR::Dump(context.sem_ir(), value_id);
}
LLVM_DUMP_METHOD static auto Dump(const Context& context,
SemIR::EntityNameId entity_name_id)
-> std::string {
return SemIR::Dump(context.sem_ir(), entity_name_id);
}
LLVM_DUMP_METHOD static auto Dump(const Context& context,
SemIR::FunctionId function_id)
-> std::string {
+26 -5
View File
@@ -12,6 +12,7 @@
#include "common/raw_string_ostream.h"
#include "llvm/ADT/APFloat.h"
#include "llvm/Support/ConvertUTF.h"
#include "llvm/Support/SaveAndRestore.h"
#include "toolchain/base/canonical_value_store.h"
#include "toolchain/base/int.h"
#include "toolchain/base/kind_switch.h"
@@ -2943,7 +2944,7 @@ static auto MakeConstantForCall(EvalContext& eval_context,
auto evaluation_mode = SemIR::Function::EvaluationMode::None;
if (auto* callee_function = std::get_if<SemIR::CalleeFunction>(&callee)) {
function = &eval_context.functions().Get(callee_function->function_id);
builtin_kind = function->builtin_function_kind();
builtin_kind = function->GetBuiltinFunctionKind(eval_context.sem_ir());
evaluation_mode = function->evaluation_mode;
// Calls to builtins and to `eval` or `musteval` functions might be
// constant.
@@ -3339,15 +3340,15 @@ static auto AddRequirementImpls(Context& context, SemIR::RequirementImpls impls,
llvm::append_range(declared_facet_type->self_impls_named_constraints,
rhs.extend_named_constraints);
} else {
auto lhs_facet_or_type = GetCanonicalFacetOrTypeValue(context, lhs_id);
auto lhs_facet = GetCanonicalFacet(context, lhs_id);
auto extends_interface = [=](SemIR::SpecificInterface si)
-> SemIR::DeclaredFacetType::TypeImplsInterface {
return {lhs_facet_or_type, si};
return {lhs_facet, si};
};
auto extends_constraint = [=](SemIR::SpecificNamedConstraint sc)
-> SemIR::DeclaredFacetType::TypeImplsNamedConstraint {
return {lhs_facet_or_type, sc};
return {lhs_facet, sc};
};
// Extend constraints are copied over without replacing anything, but are
@@ -3458,12 +3459,30 @@ auto TryEvalInstUnsafe(Context& context, SemIR::InstId inst_id,
return TryEvalInstInContext(eval_context, inst_id, inst);
}
// Update `context.access_context` to the type of the innermost enclosing type
// scope of the generic.
static auto SetAccessContext(Context& context, const SemIR::Generic& generic) {
auto function_decl =
context.insts().TryGetAs<SemIR::FunctionDecl>(generic.decl_id);
if (!function_decl || !function_decl->function_id.has_value()) {
return;
}
const auto& function = context.functions().Get(function_decl->function_id);
if (!function.parent_scope_id.has_value()) {
return;
}
context.access_context() = function.parent_scope_id;
}
auto TryEvalBlockForSpecific(Context& context, SemIR::LocId loc_id,
SemIR::SpecificId specific_id,
SemIR::GenericInstIndex::Region region) -> void {
auto generic_id = context.specifics().Get(specific_id).generic_id;
auto eval_block_id = context.generics().Get(generic_id).GetEvalBlock(region);
const auto& generic = context.generics().Get(generic_id);
auto eval_block_id = generic.GetEvalBlock(region);
auto eval_block = context.inst_blocks().Get(eval_block_id);
llvm::SaveAndRestore access_context(context.access_context());
// Allocate the value block and store it back onto the specific, so that our
// in-progress results are visible.
@@ -3476,6 +3495,8 @@ auto TryEvalBlockForSpecific(Context& context, SemIR::LocId loc_id,
}
specific.SetValueBlock(region, value_block_id);
SetAccessContext(context, generic);
EvalContext eval_context(&context, loc_id, specific_id);
Diagnostics::ContextScope diagnostic_context(
+24 -10
View File
@@ -214,24 +214,37 @@ auto EvalConstantInst(Context& context, SemIR::ExportDecl inst)
auto EvalConstantInst(Context& context, SemIR::FacetAccessType inst)
-> ConstantEvalResult {
if (auto facet_value = context.insts().TryGetAs<SemIR::FacetValue>(
inst.facet_value_inst_id)) {
auto facet = context.insts().Get(inst.facet_value_inst_id);
CARBON_CHECK(context.types().Is<SemIR::FacetType>(facet.type_id()));
// If the facet is a `type`, we can evaluate to the facet.
if (facet.type_id() == SemIR::TypeType::TypeId) {
return ConstantEvalResult::Existing(
context.constant_values().Get(inst.facet_value_inst_id));
}
// If the facet is a FacetValue, it wraps a `type`, and we can evaluate to
// that `type`.
if (auto facet_value = facet.TryAs<SemIR::FacetValue>()) {
return ConstantEvalResult::Existing(
context.constant_values().Get(facet_value->type_inst_id));
}
// The `facet_value_inst_id` is always a facet value (has type facet type).
CARBON_CHECK(context.types().Is<SemIR::FacetType>(
context.insts().Get(inst.facet_value_inst_id).type_id()));
// Other instructions (e.g. ImplWitnessAccess) of type FacetType can appear
// here, in which case the constant inst is a FacetAccessType until those
// Other instructions (e.g. ImplWitnessAccess) of type `FacetType` can appear
// here, in which case the constant inst is a `FacetAccessType` until those
// instructions resolve to one of the above.
return ConstantEvalResult::NewSamePhase(inst);
}
auto EvalConstantInst(Context& context, SemIR::FacetValue inst)
-> ConstantEvalResult {
// If the FacetValue is of type `type`, then it evaluates to the type inside
// it.
if (inst.type_id == SemIR::TypeType::TypeId) {
return ConstantEvalResult::Existing(
context.constant_values().Get(inst.type_inst_id));
}
// A FacetValue that just wraps a facet without adding/removing any witnesses
// (which means they have the same type) is evaluated to the facet itself.
if (auto access =
@@ -296,7 +309,7 @@ static auto TryFindValueInRewriteConstraints(
SemIR::ElementIndex interface_index, SemIR::InstId search_facet)
-> SemIR::ConstantId {
auto access_self_type_id = context.insts().Get(search_facet).type_id();
if (context.types().Is<SemIR::TypeType>(access_self_type_id)) {
if (access_self_type_id == SemIR::TypeType::TypeId) {
// A self facet of type `type` has no rewrite constraints to look in.
return SemIR::ConstantId::None;
}
@@ -749,7 +762,8 @@ auto EvalConstantInst(Context& context, SemIR::InstId inst_id,
SemIR::GetCalleeAsFunction(context.sem_ir(), inst.callee_id);
const auto& fn = context.functions().Get(callee_function.function_id);
if (!callee_function.self_type_id.has_value() &&
fn.builtin_function_kind() != SemIR::BuiltinFunctionKind::NoOp &&
fn.GetBuiltinFunctionKind(context.sem_ir()) !=
SemIR::BuiltinFunctionKind::NoOp &&
fn.virtual_modifier != SemIR::Function::VirtualModifier::Abstract) {
// This is not an associated function. Those will be required to be defined
// as part of checking that the impl is complete.
+1 -24
View File
@@ -467,29 +467,6 @@ auto ResolveFacetTypeRewriteConstraints(
return true;
}
auto GetEmptyFacetType(Context& context) -> SemIR::TypeId {
SemIR::DeclaredFacetTypeId declared_facet_type_id =
context.declared_facet_types().Add(SemIR::DeclaredFacetType{});
auto const_id = EvalOrAddInst<SemIR::FacetType>(
context, SemIR::LocId::None,
{.type_id = SemIR::TypeType::TypeId,
.declared_facet_type_id = declared_facet_type_id});
return context.types().GetTypeIdForTypeConstantId(const_id);
}
auto GetConstantFacetValueForType(Context& context,
SemIR::TypeInstId type_inst_id)
-> SemIR::ConstantId {
// We use an empty facet type because values of type `type` do not provide any
// witnesses of their own.
auto type_facet_type = GetEmptyFacetType(context);
return EvalOrAddInst<SemIR::FacetValue>(
context, SemIR::LocId::None,
{.type_id = type_facet_type,
.type_inst_id = type_inst_id,
.witnesses_block_id = SemIR::InstBlockId::Empty});
}
auto GetConstantFacetValueForTypeAndInterface(
Context& context, SemIR::TypeInstId type_inst_id,
SemIR::SpecificInterface specific_interface, SemIR::InstId witness_id)
@@ -550,7 +527,7 @@ auto FindWhere(Context& context, SemIR::ConstantId const_id) -> bool {
private:
bool* found_;
Set<SemIR::InstId> searched_;
Set<SemIR::InstId, 16> searched_;
};
if (!const_id.is_constant()) {
+3 -13
View File
@@ -58,19 +58,9 @@ auto ResolveFacetTypeRewriteConstraints(
llvm::SmallVector<SemIR::DeclaredFacetType::RewriteConstraint>& rewrites)
-> bool;
// Get a FacetType instruction for an empty FacetType. This is the facet
// equivalent to TypeType.
//
// TODO: We vaguely plan to replace TypeType with this FacetType in the future,
// though that's a big change.
auto GetEmptyFacetType(Context& context) -> SemIR::TypeId;
// Make a facet value for a type value, which has an empty FacetType as its
// type. Returns a constant value, whose instruction payload is a FacetValue.
auto GetConstantFacetValueForType(Context& context,
SemIR::TypeInstId type_inst_id)
-> SemIR::ConstantId;
// Make a facet value for a type value, which has a FacetType containing the
// `specific_interface` as its type. Returns a constant value, whose instruction
// payload is a FacetValue.
auto GetConstantFacetValueForTypeAndInterface(
Context& context, SemIR::TypeInstId type_inst_id,
SemIR::SpecificInterface specific_interface, SemIR::InstId witness_id)
+15 -23
View File
@@ -81,6 +81,7 @@ class FullPatternStack {
bind_name_stack_.PushArray();
var_pattern_stack_.PushArray();
next_var_index_stack_.push_back(-1);
unspecified_default_values_stack_.PushArray();
}
// Marks the start of a new full-pattern for a name binding declaration.
@@ -89,6 +90,7 @@ class FullPatternStack {
bind_name_stack_.PushArray();
var_pattern_stack_.PushArray();
next_var_index_stack_.push_back(-1);
unspecified_default_values_stack_.PushArray();
}
// Marks the start of a new full-pattern for a class `var` declaration.
@@ -97,6 +99,7 @@ class FullPatternStack {
bind_name_stack_.PushArray();
var_pattern_stack_.PushArray();
next_var_index_stack_.push_back(-1);
unspecified_default_values_stack_.PushArray();
}
// Marks the start of the current parameterized entity's implicit parameter
@@ -121,7 +124,6 @@ class FullPatternStack {
CARBON_CHECK(kind_stack_.back() == Kind::NotInEitherParamList, "{0}",
kind_stack_.back());
kind_stack_.back() = Kind::ExplicitParamList;
default_values_stack_.PushArray();
}
// Marks the end of the current parameterized entity's explicit parameter
@@ -141,16 +143,14 @@ class FullPatternStack {
// Marks the end of checking and pattern matching for the current
// full-pattern.
auto PopFullPattern() -> void {
auto kind = kind_stack_.pop_back_val();
kind_stack_.pop_back();
bind_name_stack_.PopArray();
int index = next_var_index_stack_.pop_back_val();
CARBON_CHECK(index < 0 || static_cast<size_t>(index) ==
var_pattern_stack_.PeekArray().size(),
"`GetLocalVarStorage` not called for all var patterns");
var_pattern_stack_.PopArray();
if (kind == Kind::ExplicitParamList) {
default_values_stack_.PopArray();
}
unspecified_default_values_stack_.PopArray();
}
// Records that `name_id` was introduced by the current full-pattern.
@@ -205,23 +205,15 @@ class FullPatternStack {
kind_stack_.size());
}
// Adds the inst id for a constant value provided as a default value for
// any subpattern in the full-pattern. Returns the index of that element
// as a `DefaultValueId`. Note default values are only supported for
// explicit parameter lists.
auto AddDefaultValue(SemIR::InstId inst_id) -> SemIR::DefaultValueId {
auto index = SemIR::FromRaw<SemIR::DefaultValueId>(
static_cast<int32_t>(default_values_stack_.PeekArray().size()));
CARBON_CHECK(kind_stack_.back() == Kind::ExplicitParamList);
default_values_stack_.AppendToTop(inst_id);
return index;
// Adds an unspecified pattern default value to the array at the top of the
// full pattern stack. We track these for possible later use in diagnostics.
auto AddUnspecifiedDefaultValue(SemIR::InstId inst_id) -> void {
unspecified_default_values_stack_.AppendToTop(inst_id);
}
// Returns a reference to the array of default value inst ids at the top of
// the stack. Note default values are only supported for explicit parameter
// lists.
auto GetDefaultValues() -> llvm::ArrayRef<SemIR::InstId> {
return default_values_stack_.PeekArray();
// Returns the unspecified default values array at the top of the stack.
auto GetUnspecifiedDefaultValues() -> llvm::ArrayRef<SemIR::InstId> {
return unspecified_default_values_stack_.PeekArray();
}
private:
@@ -256,9 +248,9 @@ class FullPatternStack {
// of that frame are not ready for consumption.
llvm::SmallVector<int> next_var_index_stack_;
// The stack of instructions specifying default values for subpatterns
// within this full-pattern.
ArrayStack<SemIR::InstId> default_values_stack_;
// For each full pattern we maintain a list of the InstIds of any
// unspecified default values, for use in diagnostics.
ArrayStack<SemIR::InstId> unspecified_default_values_stack_;
};
} // namespace Carbon::Check
-103
View File
@@ -91,7 +91,6 @@ struct FunctionSignatureInsts {
SemIR::InstBlockId param_patterns_id = SemIR::InstBlockId::None;
SemIR::InstBlockId call_param_patterns_id = SemIR::InstBlockId::None;
SemIR::InstBlockId call_params_id = SemIR::InstBlockId::None;
SemIR::InstBlockId call_param_default_values_id = SemIR::InstBlockId::None;
SemIR::Function::CallParamIndexRanges call_param_ranges =
SemIR::Function::CallParamIndexRanges::Empty;
SemIR::TypeInstId return_type_inst_id = SemIR::TypeInstId::None;
@@ -191,8 +190,6 @@ auto MakeGeneratedFunctionDecl(Context& context, SemIR::LocId loc_id,
{
.call_param_patterns_id = insts.call_param_patterns_id,
.call_params_id = insts.call_params_id,
.call_param_default_values_id =
insts.call_param_default_values_id,
.call_param_ranges = insts.call_param_ranges,
.return_type_inst_id = insts.return_type_inst_id,
.return_form_inst_id = insts.return_form_inst_id,
@@ -305,102 +302,6 @@ static auto CheckFunctionEvaluationModeMatches(
return false;
}
// Given a parameter patterns block, extracts the locations of all
// `SemIR::DefaultValuePattern` instructions and returns them in an array.
static auto ExtractDefaultValueLocations(Context& context,
SemIR::InstBlockId param_patterns_id)
-> llvm::SmallVector<SemIR::LocId> {
llvm::SmallVector<SemIR::LocId> locations;
for (auto inst_id : context.inst_blocks().GetOrEmpty(param_patterns_id)) {
if (context.insts().Is<SemIR::DefaultValuePattern>(inst_id)) {
locations.push_back(SemIR::LocId(inst_id));
}
}
return locations;
}
// Checks every parameter in `prev_function` and `new_function`, that if they
// both specify a default value those values are identical, or that at most
// one has an unspecified default value. If `diagnose` is true, issues
// diagnostics where either condition is violated. Returns true if every
// parameter met both criteria.
static auto CheckDefaultValueConsistency(Context& context,
const SemIR::Function& new_function,
const SemIR::Function& prev_function,
bool diagnose) -> bool {
// Both functions must either have defaults or not.
CARBON_CHECK(prev_function.call_param_default_values_id.has_value() ==
new_function.call_param_default_values_id.has_value());
if (!prev_function.call_param_default_values_id.has_value()) {
return true;
}
auto prev_value_inst_ids =
context.inst_blocks().Get(prev_function.call_param_default_values_id);
auto new_value_inst_ids =
context.inst_blocks().Get(new_function.call_param_default_values_id);
CARBON_CHECK(prev_value_inst_ids.size() == new_value_inst_ids.size());
llvm::SmallVector<size_t> indices_without_values;
llvm::SmallVector<size_t> indices_with_different_values;
for (size_t i = 0; i < prev_value_inst_ids.size(); ++i) {
bool prev_value_specified =
!context.insts().Is<SemIR::UnspecifiedValue>(prev_value_inst_ids[i]);
bool new_value_specified =
!context.insts().Is<SemIR::UnspecifiedValue>(new_value_inst_ids[i]);
if (!prev_value_specified && !new_value_specified) {
indices_without_values.push_back(i);
} else if (prev_value_specified && new_value_specified) {
auto prev_constant_id = TryEvalInst(context, prev_value_inst_ids[i]);
CARBON_CHECK(prev_constant_id != SemIR::ConstantId::NotConstant);
auto new_constant_id = TryEvalInst(context, new_value_inst_ids[i]);
CARBON_CHECK(new_constant_id != SemIR::ConstantId::NotConstant);
if (prev_constant_id != new_constant_id) {
indices_with_different_values.push_back(i);
}
}
}
bool check_ok =
indices_without_values.empty() && indices_with_different_values.empty();
if (check_ok || !diagnose) {
return check_ok;
}
// TODO: for imported functions we don't seem to have the previous parameter
// pattern block, so we can't add their locations to the diagnostic.
auto prev_param_locations =
ExtractDefaultValueLocations(context, prev_function.param_patterns_id);
auto new_param_locations =
ExtractDefaultValueLocations(context, new_function.param_patterns_id);
for (auto index : indices_without_values) {
CARBON_DIAGNOSTIC(PatternDefaultValueNeverSpecified, Error,
"no value for default number {0} is ever specified.",
size_t);
CARBON_DIAGNOSTIC(PatternDefaultValueNeverSpecifiedNote, Note,
"previous declaration here.");
auto builder = context.emitter().Build(
new_param_locations[index], PatternDefaultValueNeverSpecified, index);
if (index < prev_param_locations.size()) {
builder.Note(prev_param_locations[index],
PatternDefaultValueNeverSpecifiedNote);
}
builder.Emit();
}
for (auto index : indices_with_different_values) {
CARBON_DIAGNOSTIC(PatternDefaultValueDiffers, Error,
"default value differs from the previous declaration.");
context.emitter().Emit(new_param_locations[index],
PatternDefaultValueDiffers);
}
return false;
}
auto CheckFunctionTypeMatches(Context& context,
const SemIR::Function& new_function,
const SemIR::Function& prev_function,
@@ -419,10 +320,6 @@ auto CheckFunctionTypeMatches(Context& context,
diagnose)) {
return false;
}
if (!CheckDefaultValueConsistency(context, new_function, prev_function,
diagnose)) {
return false;
}
return true;
}
+3 -1
View File
@@ -858,7 +858,9 @@ auto MakeSpecificWithInnerSelf(Context& context, SemIR::LocId loc_id,
if (self_facet == SemIR::ErrorInst::ConstantId) {
args.push_back(SemIR::ErrorInst::InstId);
} else {
auto self_facet_inst_id = context.constant_values().GetInstId(self_facet);
// Use the canonical facet for self in order to produce fewer specifics.
auto self_facet_inst_id = context.constant_values().GetInstId(
GetCanonicalFacet(context, self_facet));
CARBON_CHECK(context.types().Is<SemIR::FacetType>(
context.insts().Get(self_facet_inst_id).type_id()));
args.push_back(self_facet_inst_id);
-1
View File
@@ -51,7 +51,6 @@ auto GlobalInit::Finalize() -> void {
.first_owning_decl_id = SemIR::InstId::None},
{.call_param_patterns_id = SemIR::InstBlockId::Empty,
.call_params_id = SemIR::InstBlockId::Empty,
.call_param_default_values_id = SemIR::InstBlockId::None,
.call_param_ranges = SemIR::Function::CallParamIndexRanges::Empty,
.return_type_inst_id = SemIR::TypeInstId::None,
.return_form_inst_id = SemIR::InstId::None,
+14 -6
View File
@@ -132,6 +132,10 @@ struct BindingPatternTypeInfo {
// For a `:?` binding this is the type component of the form denoted by
// `inst_id`. Otherwise this is the type denoted by `inst_id`.
SemIR::TypeId type_component_id;
// The instruction describing `type_component_id` as it was written. For a
// `:?` binding this is a `TypeComponentOf` the form; otherwise it is
// `inst_id` itself.
SemIR::TypeInstId type_component_inst_id;
};
} // namespace
@@ -211,7 +215,8 @@ static auto HandleAnyBindingPatternType(
auto as_type = ExprAsType(context, binding_node_id, *self_type_inst_id);
return {.node_id = binding_node_id,
.inst_id = as_type.inst_id,
.type_component_id = as_type.type_id};
.type_component_id = as_type.type_id,
.type_component_inst_id = as_type.inst_id};
}
auto [node_id, original_inst_id] = context.node_stack().PopExprWithNodeId();
@@ -277,12 +282,14 @@ static auto HandleAnyBindingPatternType(
auto as_form = FormExprAsForm(context, node_id, original_inst_id);
return {.node_id = node_id,
.inst_id = as_form.form_inst_id,
.type_component_id = as_form.type_component_id};
.type_component_id = as_form.type_component_id,
.type_component_inst_id = as_form.type_component_inst_id};
} else {
auto as_type = ExprAsType(context, node_id, original_inst_id);
return {.node_id = node_id,
.inst_id = as_type.inst_id,
.type_component_id = as_type.type_id};
.type_component_id = as_type.type_id,
.type_component_inst_id = as_type.inst_id};
}
}
@@ -339,8 +346,9 @@ static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id,
context, node_id, type_expr_region_id, type_expr.type_component_id,
{.kind = kind,
.type_id = GetPatternType(context, type_expr.type_component_id),
.entity_name_id = AddBindingEntityName(context, name_id, form_id,
/*is_unused=*/false, phase),
.entity_name_id = AddBindingEntityName(
context, name_id, type_expr.type_component_inst_id, form_id,
/*is_unused=*/false, phase),
.subpattern_id = subpattern_id});
// TODO: If `is_generic`, then `binding.bind_id is a SymbolicBinding. Subst
@@ -605,7 +613,7 @@ auto HandleParseNode(Context& context,
// compile time binding. This is popped when handling the
// CompileTimeBindingPatternId.
context.scope_stack().PushForSameRegion();
MakePeriodSelfFacetValue(context, node_id, GetEmptyFacetType(context));
MakePeriodSelfFacetValue(context, node_id, SemIR::TypeType::TypeId);
context.node_stack().Push(
node_id, SemIR::ElementIndex(context.binding_type_where_count()));
return true;
+182 -151
View File
@@ -373,6 +373,166 @@ static auto DiagnosePositionalParams(Context& context,
function_info.param_patterns_id = SemIR::InstBlockId::Empty;
}
// Diagnoses any default values for function parameters that have not been
// completely specified, which is a requirement on the first owning declaration
// of a function.
static auto DiagnoseDefaultValuesNotSpecified(
Context& context, llvm::ArrayRef<SemIR::InstId> unspecified_inst_ids)
-> void {
for (auto inst_id : unspecified_inst_ids) {
CARBON_DIAGNOSTIC(PatternDefaultValueNotSpecified, Error,
"found unspecified default parameter value in the "
"function's first owning declaration");
context.emitter().Emit(inst_id, PatternDefaultValueNotSpecified);
}
}
// For the top-level parameter patterns list, and for any level of nested tuple
// patterns, ensure that if a subpattern provides a default value, all
// subsequent patterns at that level of nesting must provide a default value as
// well. Returns the number of default values provided at the top level of the
// function parameter, useful for efficient arity checking in callers later on.
//
// TODO: per https://github.com/carbon-language/carbon-lang/issues/7529, this
// should also consider automatically supplied defaults for fully-specified
// tuple subpatterns, and consider them as having a default for the purposes
// of the out-of-order detection. It will also need to detect the error
// condition when a default is also specified for those fully-specified tuple
// subpatterns.
static auto CheckDefaults(Context& context, SemIR::Function& function)
-> int32_t {
if (!function.param_patterns_id.has_value()) {
return 0;
}
struct PatternLevelState {
// The inst ids of the subpatterns on this level of tuple subpattern
// nesting, treated as a work list, so in reverse order of declaration.
llvm::SmallVector<SemIR::InstId> subpattern_ids;
// If patterns at this level of nesting have default values, this refers
// to the first instruction to specify a default, useful for diagnostics.
SemIR::InstId first_pattern_with_default = SemIR::InstId::None;
// If we encounter a tuple-pattern during processing, we suspend processing
// of this pattern level, in the middle of processing a single pattern from
// root to leaves. So we record the current state of processing of a single
// pattern to return to it after processing any tuple subpatterns.
// True if the current pattern being processed has a default value
// specified.
bool current_pattern_has_default = false;
// The current pattern we are processing, stored separately since it's been
// popped from the `pattern_work_list` and already processed, just may need
// subsequent processing.
SemIR::InstId current_id = SemIR::InstId::None;
// A work list of patterns to be processed at this level of nesting.
llvm::SmallVector<SemIR::InstId> pattern_work_list;
// A list of subpatterns missing required defaults, to coalesce error
// reporting into a single diagnostic.
llvm::SmallVector<SemIR::InstId> patterns_missing_defaults;
// A count of the number of patterns on this level that have defaults.
int32_t default_count = 0;
};
llvm::SmallVector<PatternLevelState> level_state_stack;
size_t default_count = 0;
level_state_stack.push_back({});
llvm::append_range(
level_state_stack.back().subpattern_ids,
llvm::reverse(context.inst_blocks().Get(function.param_patterns_id)));
while (!level_state_stack.empty()) {
PatternLevelState* state = &level_state_stack.back();
while (!state->subpattern_ids.empty() ||
!state->pattern_work_list.empty() || state->current_id.has_value()) {
// If we're not resuming processing a pattern from a nested state, start
// processing the next subpattern.
if (!state->current_id.has_value()) {
state->pattern_work_list.push_back(
state->subpattern_ids.pop_back_val());
state->current_pattern_has_default = false;
}
while (!state->pattern_work_list.empty()) {
state->current_id = state->pattern_work_list.pop_back_val();
auto inst = context.insts().Get(state->current_id);
CARBON_KIND_SWITCH(inst) {
case CARBON_KIND(SemIR::DefaultValuePattern default_value_pattern): {
state->current_pattern_has_default = true;
state->default_count += 1;
state->pattern_work_list.push_back(
default_value_pattern.subpattern_id);
break;
}
case CARBON_KIND(
SemIR::WrapperBindingPattern wrapper_binding_pattern): {
state->pattern_work_list.push_back(
wrapper_binding_pattern.subpattern_id);
break;
}
case CARBON_KIND(SemIR::TuplePattern tuple_pattern): {
auto elements =
context.inst_blocks().Get(tuple_pattern.elements_id);
if (!elements.empty()) {
// Start a new state for the nested tuple pattern elements.
level_state_stack.push_back({});
state = &level_state_stack.back();
llvm::append_range(state->subpattern_ids,
llvm::reverse(elements));
}
break;
}
default:
// We only process patterns containing subpatterns, so this is an
// intentional no-op.
break;
}
}
// Finished processing this subpattern, detect a missing default if
// required.
if (state->current_pattern_has_default &&
!state->first_pattern_with_default.has_value()) {
state->first_pattern_with_default = state->current_id;
} else if (!state->current_pattern_has_default &&
state->first_pattern_with_default.has_value()) {
state->patterns_missing_defaults.push_back(state->current_id);
}
state->current_id = SemIR::InstId::None;
}
// Finished processing this tuple-pattern, emit diagnostics if any.
if (!state->patterns_missing_defaults.empty()) {
CARBON_DIAGNOSTIC(RequiredPatternDefaultValueMissing, Error,
"this pattern is missing a required default value.");
CARBON_DIAGNOSTIC(RequiredPatternDefaultValueFirstDefault, Note,
"all patterns to the right of this first pattern with "
"a default value must also specify a default value.");
CARBON_DIAGNOSTIC(
RequiredPatternDefaultValueMissingAdditional, Note,
"this pattern is also missing a required default value.");
auto inst_ref = llvm::ArrayRef(state->patterns_missing_defaults);
auto builder = context.emitter().Build(
inst_ref.consume_front(), RequiredPatternDefaultValueMissing);
for (auto inst_id : inst_ref) {
builder.Note(inst_id, RequiredPatternDefaultValueMissingAdditional);
}
builder.Note(state->first_pattern_with_default,
RequiredPatternDefaultValueFirstDefault);
builder.Emit();
}
// Extract the count from the level we just completed, overwriting any
// nested level value extracted previously.
default_count = level_state_stack.back().default_count;
level_state_stack.pop_back();
}
return default_count;
}
// Build a FunctionDecl describing the signature of a function. This
// handles the common logic shared by function declaration syntax and function
// definition syntax.
@@ -436,27 +596,33 @@ static auto BuildFunctionDecl(Context& context,
// Build the function entity. This will be merged into an existing function if
// there is one, or otherwise added to the function store.
auto function_info = SemIR::Function{
name_context.MakeEntityWithParamsBase(name, decl_id, is_extern,
introducer.extern_library),
{
.call_param_patterns_id = name.call_param_patterns_id,
.call_params_id = name.call_params_id,
.call_param_default_values_id = name.call_param_default_values_id,
.call_param_ranges = name.param_ranges,
.return_type_inst_id = return_type_inst_id,
.return_form_inst_id = return_form_inst_id,
.return_pattern_id = return_pattern_id,
.virtual_modifier = virtual_modifier,
.evaluation_mode = evaluation_mode,
.interface_modifier = interface_modifier,
.self_param_id = self_param_id,
}};
auto function_info =
SemIR::Function{name_context.MakeEntityWithParamsBase(
name, decl_id, is_extern, introducer.extern_library),
{
.call_param_patterns_id = name.call_param_patterns_id,
.call_params_id = name.call_params_id,
.call_param_ranges = name.param_ranges,
.return_type_inst_id = return_type_inst_id,
.return_form_inst_id = return_form_inst_id,
.return_pattern_id = return_pattern_id,
.virtual_modifier = virtual_modifier,
.evaluation_mode = evaluation_mode,
.interface_modifier = interface_modifier,
.self_param_id = self_param_id,
}};
if (is_definition) {
function_info.definition_id = decl_id;
}
function_info.default_value_arity = CheckDefaults(context, function_info);
DiagnosePositionalParams(context, function_info);
if (name_context.state != DeclNameStack::NameContext::State::Poisoned &&
!name_context.prev_inst_id().has_value()) {
DiagnoseDefaultValuesNotSpecified(
context, context.inst_blocks().Get(name.unspecified_values_block_id));
}
TryMergeRedecl(
context, name_context, std::nullopt,
@@ -579,145 +745,10 @@ static auto DiagnoseUnusedMarkersWithoutDefinition(
}
}
// For the top-level parameter patterns list, and for any level of nested tuple
// patterns, ensure that if a subpattern provides a default value, all
// subsequent patterns at that level of nesting must provide a default value as
// well.
// TODO: per https://github.com/carbon-language/carbon-lang/issues/7529, this
// should also consider automatically supplied defaults for fully-specified
// tuple subpatterns, and consider them as having a default for the purposes
// of the out-of-order detection. It will also need to detect the error
// condition when a default is also specified for those fully-specified tuple
// subpatterns.
static auto DiagnoseOutOfOrderDefaults(Context& context,
SemIR::FunctionId function_id) -> void {
const auto& function = context.functions().Get(function_id);
if (!function.param_patterns_id.has_value()) {
return;
}
struct PatternLevelState {
// The inst ids of the subpatterns on this level of tuple subpattern
// nesting, treated as a work list, so in reverse order of declaration.
llvm::SmallVector<SemIR::InstId> subpattern_ids;
// If patterns at this level of nesting have default values, this refers
// to the first instruction to specify a default, useful for diagnostics.
SemIR::InstId first_pattern_with_default = SemIR::InstId::None;
// If we encounter a tuple-pattern during processing, we suspend processing
// of this pattern level, in the middle of processing a single pattern from
// root to leaves. So we record the current state of processing of a single
// pattern to return to it after processing any tuple subpatterns.
// True if the current pattern being processed has a default value
// specified.
bool current_pattern_has_default = false;
// The current pattern we are processing, stored separately since it's been
// popped from the `pattern_work_list` and already processed, just may need
// subsequent processing.
SemIR::InstId current_id = SemIR::InstId::None;
// A work list of patterns to be processed at this level of nesting.
llvm::SmallVector<SemIR::InstId> pattern_work_list;
// A list of subpatterns missing required defaults, to coalesce error
// reporting into a single diagnostic.
llvm::SmallVector<SemIR::InstId> patterns_missing_defaults;
};
llvm::SmallVector<PatternLevelState> level_state_stack;
level_state_stack.push_back({});
llvm::append_range(
level_state_stack.back().subpattern_ids,
llvm::reverse(context.inst_blocks().Get(function.param_patterns_id)));
while (!level_state_stack.empty()) {
PatternLevelState* state = &level_state_stack.back();
while (!state->subpattern_ids.empty() ||
!state->pattern_work_list.empty() || state->current_id.has_value()) {
// If we're not resuming processing a pattern from a nested state, start
// processing the next subpattern.
if (!state->current_id.has_value()) {
state->pattern_work_list.push_back(
state->subpattern_ids.pop_back_val());
state->current_pattern_has_default = false;
}
while (!state->pattern_work_list.empty()) {
state->current_id = state->pattern_work_list.pop_back_val();
auto inst = context.insts().Get(state->current_id);
CARBON_KIND_SWITCH(inst) {
case CARBON_KIND(SemIR::DefaultValuePattern default_value_pattern): {
state->current_pattern_has_default = true;
state->pattern_work_list.push_back(
default_value_pattern.subpattern_id);
break;
}
case CARBON_KIND(
SemIR::WrapperBindingPattern wrapper_binding_pattern): {
state->pattern_work_list.push_back(
wrapper_binding_pattern.subpattern_id);
break;
}
case CARBON_KIND(SemIR::TuplePattern tuple_pattern): {
auto elements =
context.inst_blocks().Get(tuple_pattern.elements_id);
if (!elements.empty()) {
// Start a new state for the nested tuple pattern elements.
level_state_stack.push_back({});
state = &level_state_stack.back();
llvm::append_range(state->subpattern_ids,
llvm::reverse(elements));
}
break;
}
default:
// We only process patterns containing subpatterns, so this is an
// intentional no-op.
break;
}
}
// Finished processing this subpattern, detect a missing default if
// required.
if (state->current_pattern_has_default &&
!state->first_pattern_with_default.has_value()) {
state->first_pattern_with_default = state->current_id;
} else if (!state->current_pattern_has_default &&
state->first_pattern_with_default.has_value()) {
state->patterns_missing_defaults.push_back(state->current_id);
}
state->current_id = SemIR::InstId::None;
}
// Finished processing this tuple-pattern, emit diagnostics if any.
if (!state->patterns_missing_defaults.empty()) {
CARBON_DIAGNOSTIC(RequiredPatternDefaultValueMissing, Error,
"this pattern is missing a required default value.");
CARBON_DIAGNOSTIC(RequiredPatternDefaultValueFirstDefault, Note,
"all patterns to the right of this first pattern with "
"a default value must also specify a default value.");
CARBON_DIAGNOSTIC(
RequiredPatternDefaultValueMissingAdditional, Note,
"this pattern is also missing a required default value.");
auto inst_ref = llvm::ArrayRef(state->patterns_missing_defaults);
auto builder = context.emitter().Build(
inst_ref.consume_front(), RequiredPatternDefaultValueMissing);
for (auto inst_id : inst_ref) {
builder.Note(inst_id, RequiredPatternDefaultValueMissingAdditional);
}
builder.Note(state->first_pattern_with_default,
RequiredPatternDefaultValueFirstDefault);
builder.Emit();
}
level_state_stack.pop_back();
}
}
auto HandleParseNode(Context& context, Parse::FunctionDeclId node_id) -> bool {
auto [function_id, decl_id] =
BuildFunctionDecl(context, node_id, /*is_definition=*/false);
DiagnoseUnusedMarkersWithoutDefinition(context, function_id);
DiagnoseOutOfOrderDefaults(context, function_id);
context.decl_name_stack().PopScope();
return true;
}
+2 -2
View File
@@ -200,9 +200,9 @@ static auto PopImplIntroducerAndParamsAsNameComponent(
.param_patterns_id = SemIR::InstBlockId::None,
.call_param_patterns_id = SemIR::InstBlockId::None,
.call_params_id = SemIR::InstBlockId::None,
.call_param_default_values_id = SemIR::InstBlockId::None,
.param_ranges = SemIR::Function::CallParamIndexRanges::Empty,
.pattern_block_id = pattern_block_id};
.pattern_block_id = pattern_block_id,
.unspecified_values_block_id = SemIR::InstBlockId::Empty};
}
// Build an ImplDecl describing the signature of an impl. This handles the
+119 -1
View File
@@ -40,6 +40,92 @@ auto HandleParseNode(Context& context, Parse::InterfaceIntroducerId node_id)
return true;
}
static auto ValidateCoreInterfaceAssociatedFunction(
Context& context, SemIR::InstId decl_id, int index, CoreIdentifier name_id,
SemIR::Function::InterfaceModifier interface_modifier =
SemIR::Function::InterfaceModifier::None) -> bool {
auto loc_id = context.insts().GetCanonicalLocId(decl_id);
auto decl = context.insts().TryGetAs<SemIR::FunctionDecl>(decl_id);
if (!decl) {
context.TODO(loc_id, "associated entity must be a method");
return false;
}
auto fn = context.functions().Get(decl->function_id);
if (fn.name_id != context.core_identifiers().AddNameId(name_id)) {
context.TODO(loc_id,
llvm::formatv("associated function #{} must be named `{}`",
index, name_id));
return false;
}
auto call_params = context.inst_blocks().Get(fn.call_param_patterns_id);
// TODO: extend to support arbitrary parameters.
if (call_params.size() != 1) {
context.TODO(loc_id, "associated function must have exactly 1 parameter");
return false;
}
auto self = context.insts().TryGetAs<SemIR::RefParamPattern>(call_params[0]);
if (!self.has_value() || self->pretty_name_id != SemIR::NameId::SelfValue) {
context.TODO(loc_id,
"associated function must take `ref self` as its parameter");
return false;
}
// TODO: extend to support arbitrary return types.
if (fn.return_type_inst_id.has_value()) {
context.TODO(loc_id, "associated function must not have a return type");
return false;
}
if (fn.interface_modifier != interface_modifier) {
context.TODO(
loc_id,
interface_modifier == SemIR::Function::InterfaceModifier::None
? std::string(
"associated function must not have an interface modifier")
: llvm::formatv("associated function must be `{}`",
interface_modifier));
return false;
}
return true;
}
static auto ValidateCoreDestroy(Context& context, SemIR::LocId loc_id,
SemIR::InstBlockId associated_entities_id)
-> bool {
auto assoc_entities = context.inst_blocks().Get(associated_entities_id);
if (assoc_entities.size() != 3) {
context.TODO(
loc_id,
"interface `Core.Destroy` needs exactly 3 associated functions");
return false;
}
if (!ValidateCoreInterfaceAssociatedFunction(context, assoc_entities[0], 1,
CoreIdentifier::Op)) {
return false;
}
if (!ValidateCoreInterfaceAssociatedFunction(
context, assoc_entities[1], 2, CoreIdentifier::SubobjectDestroy,
SemIR::Function::InterfaceModifier::Final)) {
return false;
}
if (!ValidateCoreInterfaceAssociatedFunction(
context, assoc_entities[2], 3, CoreIdentifier::SelfDestruct,
SemIR::Function::InterfaceModifier::Final)) {
return false;
}
return true;
}
static auto BuildInterfaceDecl(Context& context,
Parse::AnyInterfaceDeclId node_id,
bool is_definition)
@@ -218,7 +304,7 @@ auto HandleParseNode(Context& context,
return true;
}
auto HandleParseNode(Context& context, Parse::InterfaceDefinitionId /*node_id*/)
auto HandleParseNode(Context& context, Parse::InterfaceDefinitionId node_id)
-> bool {
auto interface_id =
context.node_stack().Pop<Parse::NodeKind::InterfaceDefinitionStart>();
@@ -251,6 +337,38 @@ auto HandleParseNode(Context& context, Parse::InterfaceDefinitionId /*node_id*/)
// Finish the definition of interface-without-self.
FinishGenericDefinition(context, interface_info.generic_id);
if (context.sem_ir().package_id() == PackageNameId::Core) {
switch (interface_info.core_interface) {
case SemIR::CoreInterface::Destroy:
return ValidateCoreDestroy(context, node_id,
interface_info.associated_entities_id);
case SemIR::CoreInterface::AddAssignWith:
case SemIR::CoreInterface::AddWith:
case SemIR::CoreInterface::Copy:
case SemIR::CoreInterface::CppRangeForIterate:
case SemIR::CoreInterface::CppUnsafeDeref:
case SemIR::CoreInterface::Dec:
case SemIR::CoreInterface::Default:
case SemIR::CoreInterface::FloatFitsIn:
case SemIR::CoreInterface::DivAssignWith:
case SemIR::CoreInterface::DivWith:
case SemIR::CoreInterface::EqWith:
case SemIR::CoreInterface::Inc:
case SemIR::CoreInterface::IntFitsIn:
case SemIR::CoreInterface::ModAssignWith:
case SemIR::CoreInterface::ModWith:
case SemIR::CoreInterface::MulAssignWith:
case SemIR::CoreInterface::MulWith:
case SemIR::CoreInterface::Negate:
case SemIR::CoreInterface::OrderedWith:
case SemIR::CoreInterface::SubAssignWith:
case SemIR::CoreInterface::SubWith:
case SemIR::CoreInterface::Unknown:
// TODO: validate other core interfaces
return true;
}
}
// The decl_name_stack and scopes are popped by `ProcessNodeIds`.
return true;
}
+4 -6
View File
@@ -58,9 +58,8 @@ auto HandleParseNode(Context& context, Parse::ObserveEqualEqualId node_id)
context.args_type_info_stack().AddInstId(
AddInstInNoBlock<SemIR::ObserveEquivalent>(
context, node_id,
{.lhs_id = GetCanonicalFacetOrTypeValue(context, lhs_as_type.inst_id),
.rhs_id =
GetCanonicalFacetOrTypeValue(context, rhs_as_type.inst_id)}));
{.lhs_id = GetCanonicalFacet(context, lhs_as_type.inst_id),
.rhs_id = GetCanonicalFacet(context, rhs_as_type.inst_id)}));
return true;
}
@@ -90,9 +89,8 @@ auto HandleParseNode(Context& context, Parse::ObserveImplsId node_id) -> bool {
context.args_type_info_stack().AddInstId(
AddInstInNoBlock<SemIR::ObserveImpls>(
context, node_id,
{.lhs_id = GetCanonicalFacetOrTypeValue(context, lhs_as_type.inst_id),
.rhs_id =
GetCanonicalFacetOrTypeValue(context, rhs_as_type.inst_id)}));
{.lhs_id = GetCanonicalFacet(context, lhs_as_type.inst_id),
.rhs_id = GetCanonicalFacet(context, rhs_as_type.inst_id)}));
return true;
}
+17 -16
View File
@@ -154,11 +154,15 @@ auto HandleParseNode(Context& context, Parse::PatternListCommaId /*node_id*/)
auto HandleParseNode(Context& context, Parse::DefaultValueUnspecifiedId node_id)
-> bool {
context.node_stack().Push(
node_id, AddInst<SemIR::UnspecifiedValue>(
context, node_id,
{.type_id = GetSingletonType(
context, SemIR::UnspecifiedValueType::TypeInstId)}));
auto inst_id = AddInst<SemIR::UnspecifiedValue>(
context, node_id,
{.type_id =
GetSingletonType(context, SemIR::UnspecifiedValueType::TypeInstId)});
// Add the unspecified default value for later diagnostics checks.
context.full_pattern_stack().AddUnspecifiedDefaultValue(inst_id);
context.node_stack().Push(node_id, inst_id);
return true;
}
@@ -172,8 +176,6 @@ auto HandleParseNode(Context& context,
auto HandleParseNode(Context& context, Parse::DefaultValuePatternId node_id)
-> bool {
// On entry, the top of the node stack should have an expression for the
// default value. We evaluate it to get a constant.
auto [expr_node_id, expr_inst_id] = context.node_stack().PopExprWithNodeId();
// Ensure we are in an explicit parameter list, otherwise issue a diagnostic.
@@ -186,24 +188,23 @@ auto HandleParseNode(Context& context, Parse::DefaultValuePatternId node_id)
return false;
}
// Evaluate the default value constant.
auto expr_const_id = TryEvalInst(context, expr_inst_id);
if (expr_const_id == SemIR::ConstantId::NotConstant) {
CARBON_DIAGNOSTIC(PatternDefaultValueNotConstant, Error,
"default value for pattern must be constant");
"default value is not a constant");
context.emitter().Emit(
LocIdForDiagnostics(context.insts().GetCanonicalLocId(expr_inst_id)),
PatternDefaultValueNotConstant);
return false;
}
auto value_inst_id = context.constant_values().GetInstId(expr_const_id);
CARBON_CHECK(value_inst_id.has_value());
// Add the value to the default values array in the full pattern stack, for
// recovery later in the NameComponent.
auto default_value_id =
context.full_pattern_stack().AddDefaultValue(value_inst_id);
// Add the value to the default values store. We store the raw value here for
// conversion during pattern matching once the type of the pattern is known.
auto default_value_id = context.default_values().Add(
{.raw_id = expr_inst_id,
.value_id = SemIR::InstId::None,
.is_unspecified =
context.insts().Is<SemIR::UnspecifiedValue>(expr_inst_id)});
// Next on the node stack should be the pattern for which this default was
// specified. We pop that so we can issue the DefaultValuePattern in its
+1 -6
View File
@@ -42,10 +42,6 @@ static auto GetPeriodSelfType(Context& context,
auto frozen_const_id =
FreezePeriodSelf(context, extended_id.AsConstantId());
return context.types().GetTypeIdForTypeConstantId(frozen_const_id);
} else if (facet_type_type_id == SemIR::TypeType::TypeId) {
// The self may be `TypeType` in `type where X impls Y`, so we use an empty
// facet type.
return GetEmptyFacetType(context);
} else {
CARBON_CHECK(facet_type_type_id == SemIR::ErrorInst::TypeId,
"unexpected .Self type {0}", facet_type_type_id);
@@ -435,8 +431,7 @@ auto HandleParseNode(Context& context, Parse::RequirementImplsId node_id)
// Check lhs is a facet and rhs is a facet type.
auto lhs_as_type = ExprAsType(context, lhs_node, lhs_id);
auto rhs_as_type = ExprAsType(context, rhs_node, rhs_id);
if (rhs_as_type.type_id != SemIR::ErrorInst::TypeId &&
!context.types().IsFacetType(rhs_as_type.type_id)) {
if (!context.types().IsFacetTypeOrError(rhs_as_type.type_id)) {
DiagnoseImplsOnNonFacetType(context, rhs_node);
rhs_as_type.type_id = SemIR::ErrorInst::TypeId;
rhs_as_type.inst_id = SemIR::ErrorInst::TypeInstId;
+11 -9
View File
@@ -147,7 +147,7 @@ static auto ScopesMatch(Context& context, const SemIR::Impl& new_impl,
// The redecl is is an invalid scope.
CARBON_DIAGNOSTIC(ImplDeclInInvalidScope, Error,
"impl redeclation not in a declarative scope; "
"impl redeclaration not in a declarative scope; "
"redeclaration is allowed only in a class or namespace");
context.emitter().Emit(new_impl.latest_decl_id(), ImplDeclInInvalidScope);
return ImplRedeclType::DiagnosedInvalidRedecl;
@@ -528,10 +528,10 @@ auto AddImplWitnessForDeclaration(Context& context, SemIR::LocId loc_id,
// value to that type now we know the value of `Self`.
SemIR::TypeId assoc_const_type_id = assoc_constant_decl->type_id;
if (assoc_const_type_id.is_symbolic()) {
auto self_facet = GetConstantFacetValueForType(context, impl.self_id);
auto interface_with_self_specific_id = MakeSpecificWithInnerSelf(
context, loc_id, interface.generic_id, interface.generic_with_self_id,
impl.interface.specific_id, self_facet);
impl.interface.specific_id,
context.constant_values().Get(impl.self_id));
// Get the type of the associated constant in this interface with this
// value for `Self`.
@@ -736,7 +736,11 @@ auto FinishImplWitness(Context& context, const SemIR::Impl& impl) -> void {
}
if (fn.interface_modifier != InterfaceModifier::None) {
witness_value = decl_id;
// We are updating the impl witness table in-place, and we pulled this
// instruction out of a constant value in a different generic, so
// manually ensure the new value gets added to the eval block.
witness_value =
GetOrAddInstWithSpecificConstantValue(context, decl_id);
break;
} else {
CARBON_DIAGNOSTIC(
@@ -861,8 +865,8 @@ auto CheckRequireDeclsSatisfied(Context& context, SemIR::LocId loc_id,
// The IdentifiedFacetType canonicalizes the self facets, so we do the same
// for comparing with it.
auto self_const_id = GetCanonicalFacetOrTypeValue(
context, context.constant_values().Get(impl.self_id));
auto self_const_id =
GetCanonicalFacet(context, context.constant_values().Get(impl.self_id));
// We already identified the `impl.self_id` as the canonical
// `full_constraint_id`, so this should just be a cache lookup and can't fail.
@@ -932,11 +936,9 @@ auto CheckRequireDeclsSatisfied(Context& context, SemIR::LocId loc_id,
return;
}
// Make a facet value for the self type.
auto self_facet = GetConstantFacetValueForType(context, impl.self_id);
auto interface_with_self_specific_id = MakeSpecificWithInnerSelf(
context, loc_id, interface.generic_id, interface.generic_with_self_id,
impl.interface.specific_id, self_facet);
impl.interface.specific_id, context.constant_values().Get(impl.self_id));
for (auto require_id : require_ids) {
const auto& require = context.require_impls().Get(require_id);
+12 -19
View File
@@ -270,7 +270,7 @@ static auto TryGetSpecificWitnessIdForImpl(
// to the facet value here, and if the query was a FacetAccessType we did the
// same there so they still match.
auto deduced_self_const_id =
GetCanonicalFacetOrTypeValue(context, noncanonical_deduced_self_const_id);
GetCanonicalFacet(context, noncanonical_deduced_self_const_id);
if (query_self_const_id != deduced_self_const_id) {
return SemIR::ConstantId::None;
}
@@ -431,8 +431,7 @@ static auto CollectFacetWitnessSources(
// constraints.
const auto& impls = context.where_stack().back().impls;
for (auto [self_const_id, facet_type_const_id] : impls) {
auto canon_self_const_id =
GetCanonicalFacetOrTypeValue(context, self_const_id);
auto canon_self_const_id = GetCanonicalFacet(context, self_const_id);
// TypeType (and ErrorInst) is never stored in the impls stack, so we
// always have a FacetType in `facet_type_const_id`.
auto identified_id = TryToIdentifyFacetType(
@@ -889,19 +888,14 @@ static auto FindNonFinalWitness(
}
}
// TODO: Remove SpecificInterfaceId from LookupCustomWitness apis, switch to
// just SpecificInterface.
auto query_specific_interface_id =
context.specific_interfaces().Add(req_specific_interface);
// Consider a custom witness for core interfaces.
// TODO: This needs to expand to more interfaces, and we might want to have
// that dispatch in custom_witness.cpp instead of here.
auto core_interface =
GetCoreInterface(context, req_specific_interface.interface_id);
if (auto witness_id = LookupCustomWitness(
context, loc_id, core_interface, req_self_const_id,
query_specific_interface_id, false)) {
if (auto witness_id = LookupCustomWitness(context, loc_id, core_interface,
req_self_const_id,
req_specific_interface, false)) {
// If there's a final witness, we would have already found it via evaluating
// the LookupImplWitness instruction.
CARBON_CHECK(!witness_id->has_value());
@@ -988,8 +982,7 @@ auto LookupImplWitness(Context& context, SemIR::LocId loc_id,
context.insts()
.Get(context.constant_values().GetInstId(query_self_const_id))
.type_id();
CARBON_CHECK((context.types().IsOneOf<SemIR::TypeType, SemIR::FacetType>(
query_self_type_id)));
CARBON_CHECK(context.types().Is<SemIR::FacetType>(query_self_type_id));
// The query facet type value is indeed a facet type.
CARBON_CHECK(context.constant_values().InstIs<SemIR::FacetType>(
query_facet_type_const_id));
@@ -1127,8 +1120,8 @@ auto GetCanonicalQuerySelfForLookupImplWitness(Context& context,
// LookupImplWitness instruction, avoiding multiple constant values for
// `<facet value>` and `<facet value> as type`, which always have the same
// lookup result.
return GetCanonicalFacetOrTypeValue(
context, context.constant_values().Get(self_inst_id));
return GetCanonicalFacet(context,
context.constant_values().Get(self_inst_id));
}
// Record the query which found a final impl witness. It's illegal to
@@ -1176,7 +1169,7 @@ auto EvalLookupSingleFinalWitness(Context& context, SemIR::LocId loc_id,
context.specific_interfaces().Get(eval_query.query_specific_interface_id);
// Ensure specifics don't substitute in weird things for the query self.
CARBON_CHECK(context.types().IsFacetType(
CARBON_CHECK(context.types().Is<SemIR::FacetType>(
context.insts().Get(eval_query.query_self_inst_id).type_id()));
SemIR::ConstantId query_self_const_id =
context.constant_values().Get(eval_query.query_self_inst_id);
@@ -1275,7 +1268,7 @@ auto EvalLookupSingleFinalWitness(Context& context, SemIR::LocId loc_id,
bool used_custom_witness = false;
if (auto witness_inst_id = LookupCustomWitness(
context, loc_id, core_interface, query_self_const_id,
eval_query.query_specific_interface_id, true)) {
query_specific_interface, true)) {
if (witness_inst_id->has_value()) {
lookup_result = {.witness_id =
context.constant_values().Get(*witness_inst_id)};
@@ -1316,8 +1309,8 @@ auto EvalLookupSingleFinalWitness(Context& context, SemIR::LocId loc_id,
// `impl` we may have found in Carbon.
auto cpp_witness_id = LookupCppImpl(
context, loc_id, core_interface, query_self_const_id,
eval_query.query_specific_interface_id,
lookup_result.impl_type_structure, lookup_result.impl_loc_id);
query_specific_interface, lookup_result.impl_type_structure,
lookup_result.impl_loc_id);
if (cpp_witness_id.has_value()) {
lookup_result = {.witness_id =
context.constant_values().Get(cpp_witness_id)};
+140 -34
View File
@@ -25,8 +25,10 @@
#include "toolchain/check/type.h"
#include "toolchain/check/type_completion.h"
#include "toolchain/parse/node_ids.h"
#include "toolchain/sem_ir/builtin_function_kind.h"
#include "toolchain/sem_ir/constant.h"
#include "toolchain/sem_ir/file.h"
#include "toolchain/sem_ir/function.h"
#include "toolchain/sem_ir/identified_facet_type.h"
#include "toolchain/sem_ir/ids.h"
#include "toolchain/sem_ir/impl.h"
@@ -36,6 +38,7 @@
#include "toolchain/sem_ir/inst_kind.h"
#include "toolchain/sem_ir/name_scope.h"
#include "toolchain/sem_ir/observe.h"
#include "toolchain/sem_ir/singleton_insts.h"
#include "toolchain/sem_ir/specific_interface.h"
#include "toolchain/sem_ir/specific_named_constraint.h"
#include "toolchain/sem_ir/type_info.h"
@@ -177,6 +180,9 @@ class ImportContext {
auto import_constant_values() -> const SemIR::ConstantValueStore& {
return import_ir().constant_values();
}
auto import_default_values() -> const SemIR::DefaultValueStore& {
return import_ir().default_values();
}
auto import_entity_names() -> const SemIR::EntityNameStore& {
return import_ir().entity_names();
}
@@ -265,6 +271,9 @@ class ImportContext {
auto local_constant_values() -> SemIR::ConstantValueStore& {
return local_ir().constant_values();
}
auto local_default_values() -> SemIR::DefaultValueStore& {
return local_ir().default_values();
}
auto local_entity_names() -> SemIR::EntityNameStore& {
return local_ir().entity_names();
}
@@ -2304,6 +2313,12 @@ static auto TryResolveTypedInst(ImportRefResolver& resolver,
SemIR::DefaultValuePattern inst)
-> ResolveResult {
auto subpattern = GetLocalImportRefInfo(resolver, inst.subpattern_id);
const auto& import_default_value =
resolver.import_default_values().Get(inst.default_value_id);
// We import the first owning declaration of a function, which must always
// have default values completely specified.
CARBON_CHECK(!import_default_value.is_unspecified);
auto value = GetLocalImportRefInfo(resolver, import_default_value.value_id);
if (resolver.HasNewWork()) {
return ResolveResult::Retry();
}
@@ -2314,7 +2329,10 @@ static auto TryResolveTypedInst(ImportRefResolver& resolver,
.type_id = resolver.local_types().GetTypeIdForTypeConstantId(
subpattern.local_type_const_id),
.subpattern_id = AddLoadedImportRef(resolver, subpattern),
.default_value_id = inst.default_value_id,
.default_value_id = resolver.local_default_values().Add(
{.raw_id = SemIR::InstId::None,
.value_id = AddLoadedImportRef(resolver, value),
.is_unspecified = false}),
});
}
@@ -2406,9 +2424,10 @@ static auto TryResolveTypedInst(ImportRefResolver& resolver,
// Make a declaration of a function. This is done as a separate step from
// importing the function declaration in order to resolve cycles.
static auto ImportFunctionDecl(ImportContext& context,
const SemIR::Function& import_function,
SemIR::SpecificId specific_id)
static auto ImportFunctionDecl(
ImportContext& context, const SemIR::Function& import_function,
SemIR::SpecificId specific_id,
SemIR::GeneratedFunction::CanonicalKey generated_function_key)
-> std::pair<SemIR::FunctionId, SemIR::ConstantId> {
SemIR::FunctionDecl function_decl = {
.type_id = SemIR::TypeId::None,
@@ -2422,7 +2441,6 @@ static auto ImportFunctionDecl(ImportContext& context,
{GetIncompleteLocalEntityBase(context, function_decl_id, import_function),
{.call_param_patterns_id = SemIR::InstBlockId::None,
.call_params_id = SemIR::InstBlockId::None,
.call_param_default_values_id = SemIR::InstBlockId::None,
.call_param_ranges = import_function.call_param_ranges,
.return_type_inst_id = SemIR::TypeInstId::None,
.return_form_inst_id = SemIR::InstId::None,
@@ -2446,9 +2464,68 @@ static auto ImportFunctionDecl(ImportContext& context,
// Write the function ID and type into the FunctionDecl.
auto function_const_id =
ReplacePlaceholderImportedInst(context, function_decl_id, function_decl);
if (generated_function_key.specific_interface_id.has_value()) {
const auto& import_generated =
context.import_ir().generated_functions().Get(
import_function.generated_function_id());
context.local_functions()
.Get(function_decl.function_id)
.SetGenerated(context.local_ir().generated_functions().Add({
.canonical_key = generated_function_key,
.function_id = function_decl.function_id,
.decl_id = function_decl_id,
.builtin_function_kind = import_generated.builtin_function_kind,
}));
}
return {function_decl.function_id, function_const_id};
}
struct GeneratedFunctionData {
SemIR::SpecificInterface import_specific_interface;
SpecificInterfaceData specific_data;
SemIR::ConstantId self_type_const_id;
SemIR::NameId name_id;
};
static auto GetLocalGeneratedFunctionKeyData(
ImportRefResolver& resolver,
SemIR::GeneratedFunctionId import_generated_function_id)
-> std::optional<GeneratedFunctionData> {
if (!import_generated_function_id.has_value()) {
return std::nullopt;
}
const auto& import_key = resolver.import_ir()
.generated_functions()
.Get(import_generated_function_id)
.canonical_key;
auto import_specific_interface = resolver.import_specific_interfaces().Get(
import_key.specific_interface_id);
auto specific_data =
GetLocalSpecificInterfaceData(resolver, import_specific_interface);
auto self_type_const_id =
GetLocalConstantId(resolver, import_key.self_type_id);
auto name_id = GetLocalNameId(resolver, import_key.name_id);
return {
{import_specific_interface, specific_data, self_type_const_id, name_id}};
}
static auto GetLocalGeneratedFunctionKey(ImportRefResolver& resolver,
const SemIR::Function& import_function,
const GeneratedFunctionData& data)
-> SemIR::GeneratedFunction::CanonicalKey {
CARBON_CHECK(import_function.generated_function_id().has_value());
auto specific_interface = GetLocalSpecificInterface(
resolver, data.import_specific_interface, data.specific_data);
auto self_type_id = resolver.local_types().GetTypeIdForTypeConstantId(
data.self_type_const_id);
return {resolver.local_specific_interfaces().Add(specific_interface),
self_type_id, data.name_id};
}
static auto TryResolveTypedInst(ImportRefResolver& resolver,
SemIR::FunctionDecl inst,
SemIR::ConstantId function_const_id)
@@ -2462,17 +2539,41 @@ static auto TryResolveTypedInst(ImportRefResolver& resolver,
.GetAs<SemIR::FunctionType>(inst.type_id)
.specific_id;
auto specific_data = GetLocalSpecificData(resolver, import_specific_id);
auto generated_function_data = GetLocalGeneratedFunctionKeyData(
resolver, import_function.generated_function_id());
if (resolver.HasNewWork()) {
// This is the end of the first phase. Don't make a new function yet if
// we already have new work.
return ResolveResult::Retry();
}
// If the canonical Function for this generated function already exists,
// we dedupe by using it. Otherwise, we record the imported canonicalization
// key with the function.
auto generated_function_key = SemIR::GeneratedFunction::CanonicalKey{
SemIR::SpecificInterfaceId::None, SemIR::TypeId::None,
SemIR::NameId::None};
if (generated_function_data) {
// Generated functions are not generic.
CARBON_CHECK(!import_function.generic_id.has_value());
generated_function_key = GetLocalGeneratedFunctionKey(
resolver, import_function, *generated_function_data);
auto generated_id = resolver.local_ir().generated_functions().Lookup(
generated_function_key);
if (generated_id.has_value()) {
const auto& generated =
resolver.local_ir().generated_functions().Get(generated_id);
return ResolveResult::Done(
resolver.local_constant_values().Get(generated.decl_id));
}
}
// On the second phase, create a forward declaration of the function.
auto specific_id =
GetOrAddLocalSpecific(resolver, import_specific_id, specific_data);
std::tie(function_id, function_const_id) =
ImportFunctionDecl(resolver, import_function, specific_id);
std::tie(function_id, function_const_id) = ImportFunctionDecl(
resolver, import_function, specific_id, generated_function_key);
} else {
// On the third phase, compute the function ID from the constant value of
// the declaration.
@@ -2485,17 +2586,6 @@ static auto TryResolveTypedInst(ImportRefResolver& resolver,
auto call_param_patterns = GetLocalBlockImportRefInfo(
resolver, import_function.call_param_patterns_id);
auto call_param_default_values = GetLocalBlockImportRefInfo(
resolver, import_function.call_param_default_values_id);
llvm::SmallVector<SemIR::InstId> imported_default_values;
if (call_param_default_values.has_value()) {
auto import_fn = [&resolver](const auto& import_info) {
return GetLocalConstantInstId(resolver, import_info.import_inst_id);
};
llvm::append_range(imported_default_values,
llvm::map_range(*call_param_default_values, import_fn));
}
auto return_type_const_id = SemIR::ConstantId::None;
if (import_function.return_type_inst_id.has_value()) {
return_type_const_id =
@@ -2535,6 +2625,11 @@ static auto TryResolveTypedInst(ImportRefResolver& resolver,
auto thunk_specific_data = GetLocalSpecificData(
resolver, import_thunk_info ? import_thunk_info->specific_id
: SemIR::SpecificId::None);
auto thunk_override_self_type_const_id = SemIR::ConstantId::None;
if (import_thunk_info) {
thunk_override_self_type_const_id =
GetLocalConstantId(resolver, import_thunk_info->override_self_type_id);
}
auto& new_function = resolver.local_functions().Get(function_id);
if (resolver.HasNewWork()) {
@@ -2545,10 +2640,6 @@ static auto TryResolveTypedInst(ImportRefResolver& resolver,
// Add the function declaration.
new_function.call_param_patterns_id =
AddLoadedImportRefBlock(resolver, call_param_patterns);
if (call_param_default_values.has_value()) {
new_function.call_param_default_values_id =
resolver.local_inst_blocks().Add(imported_default_values);
}
new_function.parent_scope_id = parent_scope_id;
new_function.implicit_param_patterns_id =
AddLoadedImportRefBlock(resolver, implicit_param_patterns);
@@ -2570,6 +2661,7 @@ static auto TryResolveTypedInst(ImportRefResolver& resolver,
if (import_function.definition_id.has_value()) {
new_function.definition_id = new_function.first_owning_decl_id;
}
new_function.default_value_arity = import_function.default_value_arity;
switch (import_function.special_function_kind) {
case SemIR::Function::SpecialFunctionKind::CppThunk:
@@ -2577,11 +2669,13 @@ static auto TryResolveTypedInst(ImportRefResolver& resolver,
break;
}
case SemIR::Function::SpecialFunctionKind::Builtin: {
new_function.SetBuiltinFunction(import_function.builtin_function_kind());
new_function.SetBuiltinFunction(
import_function.non_generated_builtin_function_kind());
break;
}
case SemIR::Function::SpecialFunctionKind::CoreWitness: {
new_function.SetCoreWitness(import_function.builtin_function_kind());
case SemIR::Function::SpecialFunctionKind::Generated: {
// Generated function data is set during phase one when constructing the
// FunctionDecl.
break;
}
case SemIR::Function::SpecialFunctionKind::Thunk: {
@@ -2601,6 +2695,11 @@ static auto TryResolveTypedInst(ImportRefResolver& resolver,
local_thunk_info.specific_id = GetOrAddLocalSpecific(
resolver, import_thunk_info->specific_id, thunk_specific_data);
}
if (thunk_override_self_type_const_id.has_value()) {
local_thunk_info.override_self_type_id =
resolver.local_types().GetTypeIdForTypeConstantId(
thunk_override_self_type_const_id);
}
new_function.SetThunk(resolver.local_ir().thunks().Add(local_thunk_info));
break;
}
@@ -4481,8 +4580,9 @@ static auto TryResolveInstCanonical(ImportRefResolver& resolver,
"Constant value of constant instruction should refer to "
"the same instruction");
if (SemIR::IsSingletonInstId(constant_inst_id)) {
// Constants for builtins can be directly copied.
if (SemIR::IsSingletonInstId(constant_inst_id) ||
constant_inst_id == SemIR::TypeType::TypeInstId) {
// Constants for singletons and TypeType can be directly copied.
return ResolveResult::Done(
resolver.local_constant_values().Get(constant_inst_id));
}
@@ -4888,15 +4988,21 @@ auto ImportRefResolver::ResolveType(SemIR::TypeId import_type_id)
auto import_type_const_id = import_ir().types().GetConstantId(import_type_id);
CARBON_CHECK(import_type_const_id.has_value());
if (auto import_type_inst_id = import_ir().types().GetAsTypeInstId(
import_ir().constant_values().GetInstId(import_type_const_id));
SemIR::IsSingletonInstId(import_type_inst_id)) {
// Builtins don't require constant resolution; we can use them directly.
auto import_type_inst_id = import_ir().types().GetAsTypeInstId(
import_ir().constant_values().GetInstId(import_type_const_id));
// Builtin types don't require constant resolution; we can use them directly.
if (SemIR::IsSingletonInstId(import_type_inst_id)) {
// Singletons are all types, and need to go through GetSingletonType to
// complete them.
return GetSingletonType(local_context(), import_type_inst_id);
} else {
return local_types().GetTypeIdForTypeConstantId(
ResolveConstant(import_type_id.AsConstantId()));
} else if (import_type_inst_id == SemIR::TypeType::TypeInstId) {
// TypeType is the other builtin type, and is already complete.
return SemIR::TypeType::TypeId;
}
return local_types().GetTypeIdForTypeConstantId(
ResolveConstant(import_type_id.AsConstantId()));
}
auto ImportRefResolver::HasNewWork() -> bool {
+42 -56
View File
@@ -99,54 +99,41 @@ static auto IsInstanceType(Context& context, SemIR::TypeId type_id) -> bool {
return false;
}
auto GetHighestAllowedAccess(Context& context, SemIR::LocId loc_id,
auto GetHighestAllowedAccess(Context& context,
SemIR::ConstantId name_scope_const_id)
-> SemIR::AccessKind {
SemIR::ScopeLookupResult lookup_result =
LookupUnqualifiedName(context, loc_id, SemIR::NameId::SelfType,
/*required=*/false)
.scope_result;
CARBON_CHECK(!lookup_result.is_poisoned());
if (!lookup_result.is_found()) {
SemIR::NameScopeId access_context_scope_id = context.access_context();
if (!access_context_scope_id.has_value()) {
return SemIR::AccessKind::Public;
}
// TODO: Support other types for `Self`.
auto self_class_type = context.insts().TryGetAs<SemIR::ClassType>(
lookup_result.target_inst_id());
if (!self_class_type) {
return SemIR::AccessKind::Public;
}
auto self_class_info = context.classes().Get(self_class_type->class_id);
// TODO: Support other types.
if (auto class_type =
context.constant_values().TryGetInstAs<SemIR::ClassType>(
name_scope_const_id)) {
auto class_info = context.classes().Get(class_type->class_id);
if (self_class_info.self_type_id == class_info.self_type_id) {
return SemIR::AccessKind::Private;
// Check if private access is allowed.
while (access_context_scope_id.has_value()) {
if (class_info.scope_id == access_context_scope_id) {
return SemIR::AccessKind::Private;
}
const auto& scope = context.name_scopes().Get(access_context_scope_id);
access_context_scope_id = scope.parent_scope_id();
}
// If the `type_id` of `Self` does not match with the one we're currently
// accessing, try checking if this class is of the parent type of `Self`.
if (auto base_type_id = self_class_info.GetBaseType(
context.sem_ir(), self_class_type->specific_id);
base_type_id.has_value()) {
if (context.types().GetConstantId(base_type_id) == name_scope_const_id) {
return SemIR::AccessKind::Protected;
}
// TODO: Also check whether this base class has a base class of its own.
} else if (auto adapt_type_id = self_class_info.GetAdaptedType(
context.sem_ir(), self_class_type->specific_id);
adapt_type_id.has_value()) {
if (context.types().GetConstantId(adapt_type_id) == name_scope_const_id) {
// TODO: Should we be allowed to access protected fields of a type we
// are adapting? The design doesn't allow this.
// Check if protected access is allowed.
access_context_scope_id = context.access_context();
const auto& scope = context.name_scopes().Get(access_context_scope_id);
for (auto extended_scope_id : scope.extended_scopes()) {
auto const_id = context.constant_values().Get(extended_scope_id);
if (const_id == name_scope_const_id) {
return SemIR::AccessKind::Protected;
}
// TODO: also check indirectly-extended scopes, as well as extended
// scopes of parent scopes of the access context.
}
}
@@ -161,22 +148,21 @@ static auto ScopeNeedsImplLookup(Context& context,
SemIR::InstId inst_id =
context.constant_values().GetInstId(name_scope_const_id);
CARBON_CHECK(inst_id.has_value());
SemIR::Inst inst = context.insts().Get(inst_id);
if (inst.Is<SemIR::FacetType>()) {
// Don't perform impl lookup if an associated entity is named as a member of
// a facet type.
return false;
}
if (inst.Is<SemIR::Namespace>()) {
if (context.insts().Is<SemIR::Namespace>(inst_id)) {
// Don't perform impl lookup if an associated entity is named as a namespace
// member.
// TODO: This case is not yet listed in the design.
return false;
}
auto type_id = context.types().GetTypeIdForTypeInstId(inst_id);
// Don't perform impl lookup if an associated entity is named as a member of
// a constrained facet type.
//
// Any other kind of scope is assumed to be a type that implements the
// interface containing the associated entity, and impl lookup is performed.
return true;
return !context.types().IsConstrainedFacetType(type_id);
}
static auto PerformImplWitnessAccessAndSubstitute(
@@ -318,7 +304,7 @@ static auto LookupMemberNameInScope(Context& context, SemIR::LocId loc_id,
AccessInfo access_info = {
.constant_id = name_scope_const_id,
.highest_allowed_access =
GetHighestAllowedAccess(context, loc_id, name_scope_const_id),
GetHighestAllowedAccess(context, name_scope_const_id),
};
LookupResult result = LookupQualifiedName(
context, loc_id, name_id, lookup_scopes, required, access_info);
@@ -503,9 +489,8 @@ static auto PerformActionHelper(Context& context, SemIR::LocId loc_id,
SemIR::InstId base_id, SemIR::NameId name_id,
bool required) -> SemIR::InstId {
// Unwrap the facet value in `base_id` if possible.
if (auto facet_value = TryGetCanonicalFacetValue(context, base_id);
facet_value.has_value()) {
base_id = facet_value;
if (auto facet = TryGetCanonicalFacet(context, base_id); facet.has_value()) {
base_id = facet;
}
// If the base is a name scope, such as a class or namespace, perform lookup
@@ -528,7 +513,7 @@ static auto PerformActionHelper(Context& context, SemIR::LocId loc_id,
// `base_id` (as part the class case above), as the `base_id` facet should
// have member names that directly name members of the `impl`.
auto base_type_id = context.insts().Get(base_id).type_id();
if (context.types().Is<SemIR::FacetType>(base_type_id)) {
if (context.types().IsConstrainedFacetType(base_type_id)) {
// Name lookup into a facet requires the facet type to be complete, so
// that any names available through the facet type are known for the
// facet.
@@ -602,15 +587,15 @@ static auto PerformActionHelper(Context& context, SemIR::LocId loc_id,
auto lookup_const_id =
context.types().GetConstantId(unqualified_base_type_id);
// TODO: If the type is a facet, we look through it into the facet's type (a
// FacetType) for names. According to the design, we shouldn't need to do
// this, as the facet should have member names that directly name members of
// the `impl`.
auto base_type_as_facet = GetCanonicalFacetOrTypeValue(
context, context.types().GetTypeInstId(base_type_id));
// TODO: If the type is a constrained facet, we look through it into the
// facet's type (a FacetType) for names. According to the design, we shouldn't
// need to do this, as the facet should have member names that directly name
// members of the `impl`.
auto base_type_as_facet =
GetCanonicalFacet(context, context.types().GetTypeInstId(base_type_id));
auto base_type_facet_type_id =
context.insts().Get(base_type_as_facet).type_id();
if (context.types().Is<SemIR::FacetType>(base_type_facet_type_id)) {
if (context.types().IsConstrainedFacetType(base_type_facet_type_id)) {
lookup_const_id = context.types().GetConstantId(base_type_facet_type_id);
}
@@ -619,11 +604,12 @@ static auto PerformActionHelper(Context& context, SemIR::LocId loc_id,
if (AppendLookupScopesForConstant(
context, loc_id, lookup_const_id,
// The `self_type_const_id` should be the type of `base_id` even if
// it's a facet.
// it's a constrained facet.
//
// TODO: This can be replaced with `lookup_const_id` once we stop
// having to look through the facet at its type for the scope.
context.types().GetConstantId(base_type_id), /*extended_scope=*/false,
// having to look through the constrained facet at its type for the
// scope.
base_type_id.AsConstantId(), /*extended_scope=*/false,
&lookup_scopes)) {
auto member_id = LookupMemberNameInScope(
context, loc_id, base_id, name_id, lookup_const_id, lookup_scopes,
+1 -1
View File
@@ -13,7 +13,7 @@ namespace Carbon::Check {
// Returns the highest allowed access for members of `name_scope_const_id`. For
// example, if this returns `Protected` then only `Public` and `Protected`
// accesses are allowed -- not `Private`.
auto GetHighestAllowedAccess(Context& context, SemIR::LocId loc_id,
auto GetHighestAllowedAccess(Context& context,
SemIR::ConstantId name_scope_const_id)
-> SemIR::AccessKind;
+31 -47
View File
@@ -248,7 +248,13 @@ static auto CheckRedeclParam(Context& context, bool is_implicit_param,
bool check_type = true;
do {
auto patterns = pattern_stack.pop_back_val();
auto new_param_pattern = context.insts().Get(patterns.new_id);
// Typically the new decl (redecl) is a local instruction and we can just
// use the id directly. But for canonicalized Generated functions, we may
// use an imported function in place of a local decl so the `kind()` would
// be an `ImportRefLoaded`. What we want is the canonical instruction for
// the new pattern regardless.
auto new_param_pattern = context.insts().Get(
context.constant_values().GetConstantInstId(patterns.new_id));
auto prev_param_const_id = SemIR::GetConstantValueInSpecific(
context.sem_ir(), prev_specific_id, patterns.prev_id);
auto prev_param_pattern =
@@ -333,14 +339,33 @@ static auto CheckRedeclParam(Context& context, bool is_implicit_param,
auto prev_default_value_pattern =
prev_param_pattern.As<SemIR::DefaultValuePattern>();
// If the new pattern specified a default value, it must match the
// previously declared default value.
auto& new_default_value = context.default_values().Get(
new_default_value_pattern.default_value_id);
const auto& prev_default_value = context.default_values().Get(
prev_default_value_pattern.default_value_id);
if (!new_default_value.is_unspecified) {
// We require first owning declaration to always specify a default
// value.
CARBON_CHECK(!prev_default_value.is_unspecified);
auto new_constant_id =
context.constant_values().Get(new_default_value.value_id);
auto prev_constant_id =
context.constant_values().Get(prev_default_value.value_id);
if (new_constant_id != prev_constant_id) {
emit_general_diagnostic();
return false;
}
} else {
// If the new default value was left unspecified, we copy the previous
// processed default value into the new default value.
new_default_value.value_id = prev_default_value.value_id;
}
pattern_stack.push_back(
{.prev_id = prev_default_value_pattern.subpattern_id,
.new_id = new_default_value_pattern.subpattern_id});
// The node kind comparison should catch this on the mismatched patterns
// prior to this, so the indices should never mismatch.
CARBON_CHECK(prev_default_value_pattern.default_value_id.index ==
new_default_value_pattern.default_value_id.index);
break;
}
default: {
@@ -687,43 +712,6 @@ static auto FillPrevEntityInfo(Context& context,
}
}
// Updates the default values in `prev_function` to include any of those not
// previously specified and that are now specified in `new_function`.
static auto MergeFunctionParamDefaultValues(Context& context,
SemIR::Function& prev_function,
const SemIR::Function& new_function)
-> void {
CARBON_CHECK(prev_function.call_param_default_values_id.has_value() ==
new_function.call_param_default_values_id.has_value());
if (!prev_function.call_param_default_values_id.has_value()) {
return;
}
auto prev_value_inst_ids =
context.inst_blocks().Get(prev_function.call_param_default_values_id);
auto new_value_inst_ids =
context.inst_blocks().Get(new_function.call_param_default_values_id);
CARBON_CHECK(prev_value_inst_ids.size() == new_value_inst_ids.size());
llvm::SmallVector<SemIR::InstId> merged_value_inst_ids;
bool merge_has_new_info = false;
merged_value_inst_ids.reserve(prev_value_inst_ids.size());
for (size_t i = 0; i < prev_value_inst_ids.size(); ++i) {
bool had_value =
!context.insts().Is<SemIR::UnspecifiedValue>(prev_value_inst_ids[i]);
auto merged_id = had_value ? prev_value_inst_ids[i] : new_value_inst_ids[i];
merge_has_new_info |=
!had_value && !context.insts().Is<SemIR::UnspecifiedValue>(merged_id);
merged_value_inst_ids.push_back(merged_id);
}
if (merge_has_new_info) {
auto merged_block_id = context.inst_blocks().Add(merged_value_inst_ids);
prev_function.call_param_default_values_id = merged_block_id;
}
}
template <typename EntityT>
auto TryMergeRedecl(Context& context,
const DeclNameStack::NameContext& name_context,
@@ -898,10 +886,6 @@ auto TryMergeRedecl(Context& context,
if (is_definition) {
prev_entity.MergeDefinition(entity_info.new_entity);
if constexpr (IsFunction) {
MergeFunctionParamDefaultValues(context, prev_entity,
entity_info.new_entity);
}
}
auto replace_prev_inst = prev_import_ir_id.has_value();
+16 -20
View File
@@ -13,7 +13,6 @@ auto PopNameComponent(Context& context, SemIR::InstId return_pattern_id)
-> NameComponent {
Parse::NodeId first_param_node_id = Parse::NoneNodeId();
Parse::NodeId last_param_node_id = Parse::NoneNodeId();
auto call_param_default_values_id = SemIR::InstBlockId::None;
// Explicit params.
auto [params_node_id, param_patterns_id] =
@@ -24,10 +23,6 @@ auto PopNameComponent(Context& context, SemIR::InstId return_pattern_id)
context.node_stack()
.PopForSoloNodeId<Parse::NodeKind::ExplicitParamListStart>();
last_param_node_id = params_node_id;
if (!context.full_pattern_stack().GetDefaultValues().empty()) {
call_param_default_values_id = context.inst_blocks().Add(
context.full_pattern_stack().GetDefaultValues());
}
} else {
param_patterns_id = SemIR::InstBlockId::None;
}
@@ -53,6 +48,7 @@ auto PopNameComponent(Context& context, SemIR::InstId return_pattern_id)
auto call_params_id = SemIR::InstBlockId::None;
auto param_ranges = SemIR::Function::CallParamIndexRanges::Empty;
auto pattern_block_id = SemIR::InstBlockId::None;
auto unspecified_values_block_id = SemIR::InstBlockId::Empty;
if (param_patterns_id->has_value() ||
implicit_param_patterns_id->has_value() ||
return_pattern_id.has_value()) {
@@ -61,6 +57,8 @@ auto PopNameComponent(Context& context, SemIR::InstId return_pattern_id)
call_param_patterns_id = results.call_param_patterns_id;
call_params_id = results.call_params_id;
param_ranges = results.param_ranges;
unspecified_values_block_id = context.inst_blocks().Add(
context.full_pattern_stack().GetUnspecifiedDefaultValues());
pattern_block_id = context.pattern_block_stack().Pop();
context.full_pattern_stack().PopFullPattern();
}
@@ -68,21 +66,19 @@ auto PopNameComponent(Context& context, SemIR::InstId return_pattern_id)
auto [name_loc_id, name_id] =
context.node_stack().PopWithNodeId<Parse::NodeCategory::NonExprName>();
return {
.name_loc_id = name_loc_id,
.name_id = name_id,
.first_param_node_id = first_param_node_id,
.last_param_node_id = last_param_node_id,
.implicit_params_loc_id = implicit_params_node_id,
.implicit_param_patterns_id = *implicit_param_patterns_id,
.params_loc_id = params_node_id,
.param_patterns_id = *param_patterns_id,
.call_param_patterns_id = call_param_patterns_id,
.call_params_id = call_params_id,
.call_param_default_values_id = call_param_default_values_id,
.param_ranges = param_ranges,
.pattern_block_id = pattern_block_id,
};
return {.name_loc_id = name_loc_id,
.name_id = name_id,
.first_param_node_id = first_param_node_id,
.last_param_node_id = last_param_node_id,
.implicit_params_loc_id = implicit_params_node_id,
.implicit_param_patterns_id = *implicit_param_patterns_id,
.params_loc_id = params_node_id,
.param_patterns_id = *param_patterns_id,
.call_param_patterns_id = call_param_patterns_id,
.call_params_id = call_params_id,
.param_ranges = param_ranges,
.pattern_block_id = pattern_block_id,
.unspecified_values_block_id = unspecified_values_block_id};
}
// Pop the name of a declaration from the node stack, and diagnose if it has
+3 -2
View File
@@ -40,12 +40,13 @@ struct NameComponent {
// SemIR::EntityWithParamsBase).
SemIR::InstBlockId call_param_patterns_id;
SemIR::InstBlockId call_params_id;
// The pattern default values as extracted from the parameter list.
SemIR::InstBlockId call_param_default_values_id;
SemIR::Function::CallParamIndexRanges param_ranges;
// The pattern block.
SemIR::InstBlockId pattern_block_id;
// The `UnspecifiedValue` insts from the parameter default values, if any.
SemIR::InstBlockId unspecified_values_block_id;
};
// Pops a name component from the node stack (and pattern block stack, if it has
+32 -49
View File
@@ -9,7 +9,6 @@
#include "common/raw_string_ostream.h"
#include "toolchain/check/control_flow.h"
#include "toolchain/check/cpp/import.h"
#include "toolchain/check/facet_type.h"
#include "toolchain/check/generic.h"
#include "toolchain/check/import.h"
#include "toolchain/check/import_ref.h"
@@ -22,6 +21,7 @@
#include "toolchain/sem_ir/generic.h"
#include "toolchain/sem_ir/ids.h"
#include "toolchain/sem_ir/name_scope.h"
#include "toolchain/sem_ir/typed_insts.h"
namespace Carbon::Check {
@@ -297,50 +297,24 @@ struct ProhibitedAccessInfo {
static auto GetSelfFacetForInterfaceFromLookupSelfType(
Context& context, const SemIR::GenericId generic_with_self_id,
SemIR::ConstantId self_type_const_id) -> SemIR::ConstantId {
if (!self_type_const_id.has_value()) {
// In a lookup into a non-lexical scope, there is no self-type from the
// lookup for the interface-with-self specific. So the self-type we use is
// the abstract symbolic Self from the self specific of the
// interface-with-self.
auto self_specific_args_id = context.specifics().GetArgsOrEmpty(
context.generics().GetSelfSpecific(generic_with_self_id));
auto self_specific_args = context.inst_blocks().Get(self_specific_args_id);
return context.constant_values().Get(self_specific_args.back());
if (self_type_const_id.has_value() &&
!context.constant_values().InstIs<SemIR::FacetType>(self_type_const_id)) {
// Extended name lookup into the type of `x`, such as in a member access
// `x.F`. We can find a facet type extended scope from the type of `x`.
return self_type_const_id;
}
if (context.constant_values().InstIs<SemIR::FacetType>(self_type_const_id)) {
// We are looking directly in a facet type, like `I.F` for an interface `I`,
// which means there is no self-type from the lookup for the
// interface-with-self specific. So the self-type we use is the abstract
// symbolic Self from the self specific of the interface-with-self.
auto self_specific_args_id = context.specifics().GetArgsOrEmpty(
context.generics().GetSelfSpecific(generic_with_self_id));
auto self_specific_args = context.inst_blocks().Get(self_specific_args_id);
return context.constant_values().Get(self_specific_args.back());
}
// Extended name lookup into a type, like `x.F`, can find a facet
// type extended scope from the type of `x`. The type of `x` maybe a
// facet converted to a type, so drop the `as type` conversion if
// so.
auto canonical_facet_or_type =
GetCanonicalFacetOrTypeValue(context, self_type_const_id);
auto type_of_canonical_facet_or_type =
context.insts()
.Get(context.constant_values().GetInstId(canonical_facet_or_type))
.type_id();
if (type_of_canonical_facet_or_type == SemIR::TypeType::TypeId) {
// If we still have a type, turn it into a facet for use in the
// interface-with-self specific.
return GetConstantFacetValueForType(
context, context.types().GetAsTypeInstId(
context.constant_values().GetInstId(self_type_const_id)));
}
// We have a facet for the self-type (or perhaps an ErrorInst), which we can
// use directly in the interface-with-self specific.
return canonical_facet_or_type;
// If `self_type_const_id` is None, we are doing lookup into a non-lexical
// scope. If it is a `FacetType`, then we are doing lookup directly on a facet
// type, such as `I.F` on an interface `I`.
//
// In these cases, there is no self-type from the lookup for the
// interface-with-self specific. So the self-type we use is the abstract
// symbolic Self from the self specific of the interface-with-self.
auto self_specific_args_id = context.specifics().GetArgsOrEmpty(
context.generics().GetSelfSpecific(generic_with_self_id));
auto self_specific_args = context.inst_blocks().Get(self_specific_args_id);
return context.constant_values().Get(self_specific_args.back());
}
auto AppendLookupScopesForConstant(Context& context, SemIR::LocId loc_id,
@@ -350,15 +324,16 @@ auto AppendLookupScopesForConstant(Context& context, SemIR::LocId loc_id,
llvm::SmallVector<LookupScope>* scopes)
-> bool {
auto lookup_inst_id = context.constant_values().GetInstId(lookup_const_id);
auto lookup = context.insts().Get(lookup_inst_id);
if (auto ns = lookup.TryAs<SemIR::Namespace>()) {
if (auto ns = context.insts().TryGetAs<SemIR::Namespace>(lookup_inst_id)) {
scopes->push_back(LookupScope{.name_scope_id = ns->name_scope_id,
.specific_id = SemIR::SpecificId::None,
.self_const_id = SemIR::ConstantId::None});
return true;
}
if (auto class_ty = lookup.TryAs<SemIR::ClassType>()) {
if (auto class_ty =
context.insts().TryGetAs<SemIR::ClassType>(lookup_inst_id)) {
if (!extended_scope) {
// TODO: Allow name lookup into classes that are being defined even if
// they are not complete.
@@ -378,8 +353,14 @@ auto AppendLookupScopesForConstant(Context& context, SemIR::LocId loc_id,
.self_const_id = self_type_const_id});
return true;
}
// Extended scopes may point to a FacetType.
if (auto facet_type = lookup.TryAs<SemIR::FacetType>()) {
// Extended scopes may point to a FacetType. If it has constraints, collect
// the extended ones as scopes.
auto lookup_type_id =
context.types().TryGetTypeIdForTypeInstId(lookup_inst_id);
if (lookup_type_id.has_value() &&
context.types().IsConstrainedFacetType(lookup_type_id)) {
auto facet_type = context.types().GetAs<SemIR::FacetType>(lookup_type_id);
if (!extended_scope) {
// TODO: Allow name lookup into facet types that are being defined even if
// they are not complete.
@@ -405,7 +386,7 @@ auto AppendLookupScopesForConstant(Context& context, SemIR::LocId loc_id,
}
auto declared_facet_type =
context.declared_facet_types().Get(facet_type->declared_facet_type_id);
context.declared_facet_types().Get(facet_type.declared_facet_type_id);
// Name lookup into "extend" constraints but not "self impls" constraints.
for (const auto& extend : declared_facet_type.extend_constraints) {
auto& interface = context.interfaces().Get(extend.interface_id);
@@ -440,6 +421,7 @@ auto AppendLookupScopesForConstant(Context& context, SemIR::LocId loc_id,
}
return true;
}
if (lookup_const_id == SemIR::ErrorInst::ConstantId) {
// Lookup into this scope should fail without producing an error.
scopes->push_back(LookupScope{.name_scope_id = SemIR::NameScopeId::None,
@@ -447,6 +429,7 @@ auto AppendLookupScopesForConstant(Context& context, SemIR::LocId loc_id,
.self_const_id = SemIR::ConstantId::None});
return true;
}
// TODO: Per the design, if `base_id` is any kind of type, then lookup should
// treat it as a name scope, even if it doesn't have members. For example,
// `(i32*).X` should fail because there's no name `X` in `i32*`, not because
+12 -6
View File
@@ -92,8 +92,9 @@ auto MakeEmptyRegion(Context& context, SemIR::InstId result_id)
}
auto AddBindingEntityName(Context& context, SemIR::NameId name_id,
SemIR::InstId form_id, bool is_unused,
BindingPhase phase) -> SemIR::EntityNameId {
SemIR::TypeInstId type_inst_id, SemIR::InstId form_id,
bool is_unused, BindingPhase phase)
-> SemIR::EntityNameId {
SemIR::EntityName entity_name = {
.name_id = name_id,
.parent_scope_id = context.scope_stack().PeekNameScopeId(),
@@ -104,6 +105,7 @@ auto AddBindingEntityName(Context& context, SemIR::NameId name_id,
entity_name.is_template = phase == BindingPhase::Template;
}
entity_name.form_id = form_id;
entity_name.type_inst_id = type_inst_id;
return context.entity_names().Add(entity_name);
}
@@ -243,10 +245,14 @@ auto AddParamPattern(Context& context, SemIR::LocId loc_id,
}
}();
auto entity_name_id = AddBindingEntityName(context, name_id,
/*form_id=*/SemIR::InstId::None,
/*is_unused=*/false,
/*phase=*/BindingPhase::Runtime);
// This pattern is synthesized rather than written in the source, so there is
// no spelling to record for its type.
auto entity_name_id =
AddBindingEntityName(context, name_id,
/*type_inst_id=*/SemIR::TypeInstId::None,
/*form_id=*/SemIR::InstId::None,
/*is_unused=*/false,
/*phase=*/BindingPhase::Runtime);
auto pattern_type_id = GetPatternType(context, type_id);
if (kind == ParamPatternKind::Var) {
+5 -2
View File
@@ -58,9 +58,12 @@ struct BindingPatternInfo {
enum class BindingPhase { Template, Symbolic, Runtime };
// Creates an entity name for a binding pattern with the given properties.
// `type_inst_id` is the declared type of the binding as written, if known; see
// `SemIR::EntityName::type_inst_id`.
auto AddBindingEntityName(Context& context, SemIR::NameId name_id,
SemIR::InstId form_id, bool is_unused,
BindingPhase phase) -> SemIR::EntityNameId;
SemIR::TypeInstId type_inst_id, SemIR::InstId form_id,
bool is_unused, BindingPhase phase)
-> SemIR::EntityNameId;
// Creates a binding pattern and the associated binding inst, and returns their
// IDs. `scrutinee_type_id` is the type of the binding, and `type_region_id` is
+46 -30
View File
@@ -119,7 +119,9 @@ using State =
class MatchContext {
public:
struct PreWork : Printable<PreWork> {
// `None` when processing the callee side.
// `None` when processing the callee side, or when processing the caller
// side and no value was supplied, in expectation of using a default value
// from the corresponding callee pattern.
SemIR::InstId scrutinee_id;
auto Print(llvm::raw_ostream& out) const -> void {
@@ -936,13 +938,31 @@ auto MatchContext::DoPreWork(State state,
SemIR::DefaultValuePattern default_value_pattern,
SemIR::InstId scrutinee_id, WorkItem entry)
-> void {
if (!std::holds_alternative<CalleeState*>(state)) {
CARBON_FATAL("Unhandled state kind in DefaultValuePattern pre-work");
CARBON_KIND_SWITCH(state) {
case CARBON_KIND(CallerState* _): {
// If there's no scrutinee supplied, supply the default value instead.
if (!scrutinee_id.has_value()) {
const auto& default_value = context_.default_values().Get(
default_value_pattern.default_value_id);
CARBON_CHECK(default_value.value_id.has_value());
auto [inst_id, _] = WrapInstForSpecific(
context_, SemIR::LocId(default_value.value_id),
default_value.value_id, specific_id_stack_.back());
scrutinee_id = inst_id;
}
break;
}
case CARBON_KIND(CalleeState* _): {
// We will need to check the type of the parameter to make sure it
// matches the provided default, so add ourselves to the post-work list.
results_stack_.PushArray();
AddAsPostWork(entry);
break;
}
default: {
CARBON_FATAL("Unhandled state kind in DefaultValuePattern pre-work");
}
}
// We will need to check the type of the parameter to make sure it
// matches the provided default, so add ourselves to the post-work list.
results_stack_.PushArray();
AddAsPostWork(entry);
// Process the subpattern for the default.
AddWork({.pattern_id = default_value_pattern.subpattern_id,
@@ -952,7 +972,7 @@ auto MatchContext::DoPreWork(State state,
auto MatchContext::DoPostWork(State state,
SemIR::DefaultValuePattern default_value_pattern,
WorkItem entry) -> void {
WorkItem /*entry*/) -> void {
if (!std::holds_alternative<CalleeState*>(state)) {
CARBON_FATAL("Unhandled state kind in DefaultValuePattern post-work");
}
@@ -960,30 +980,16 @@ auto MatchContext::DoPostWork(State state,
auto param_inst_id = results_stack_.PeekArray().back();
auto param_type_id = context_.insts().Get(param_inst_id).type_id();
auto default_value_inst_id =
context_.full_pattern_stack()
.GetDefaultValues()[default_value_pattern.default_value_id.index];
// If a constant was specified, we should be able to convert it into the
// type of the parameter.
if (!context_.insts().Is<SemIR::UnspecifiedValue>(default_value_inst_id)) {
// We should be able to convert the supplied constant into the type of
// the parameter.
auto converted_id =
TryConvertToValueOfType(context_, SemIR::LocId(default_value_inst_id),
default_value_inst_id, param_type_id);
if (converted_id == SemIR::ErrorInst::InstId) {
CARBON_DIAGNOSTIC(
PatternDefaultValueTypeMismatch, Error,
"default value expression type {0} doesn't match pattern type {1}",
TypeOfInstId, TypeOfInstId);
// TODO: should be able to provide precise locations for both default
// value expression and the type of the pattern, but we can't because
// they are both constants.
context_.emitter().Emit(entry.pattern_id, PatternDefaultValueTypeMismatch,
default_value_inst_id, param_inst_id);
}
auto& default_value =
context_.default_values().Get(default_value_pattern.default_value_id);
if (!default_value.is_unspecified) {
default_value.value_id =
ConvertToValueOfType(context_, SemIR::LocId(default_value.raw_id),
default_value.raw_id, param_type_id);
}
results_stack_.PopArray();
// If something at a higher level in the stack needed these results, bubble
@@ -1240,9 +1246,19 @@ auto CallerPatternMatch(Context& context, SemIR::SpecificId specific_id,
CARBON_CHECK(self_pattern_id.has_value());
}
for (const auto& [arg_id, param_pattern_id] : llvm::zip_equal(
// `arg_refs` may have a smaller arity than `param_patterns_id` due to the
// possible presence of default values for some parameters. We use
// `zip_longest` here to allow for that size disparity. But we presume we
// always have a parameter pattern, so test that presumption here.
CARBON_CHECK(self_arg_refs.size() + arg_refs.size() <=
context.inst_blocks().GetOrEmpty(param_patterns_id).size());
for (const auto& [maybe_arg_id, maybe_param_pattern_id] : llvm::zip_longest(
llvm::concat<const SemIR::InstId>(self_arg_refs, arg_refs),
context.inst_blocks().GetOrEmpty(param_patterns_id))) {
CARBON_CHECK(maybe_param_pattern_id.has_value());
const auto& param_pattern_id = *maybe_param_pattern_id;
const auto& arg_id =
maybe_arg_id.has_value() ? *maybe_arg_id : SemIR::InstId::None;
match.Match(&state,
{.pattern_id = param_pattern_id,
.work = MatchContext::PreWork{.scrutinee_id = arg_id},
+15 -28
View File
@@ -22,8 +22,7 @@ namespace Carbon::Check {
auto MakePeriodSelfFacetValue(Context& context, SemIR::LocId loc_id,
SemIR::TypeId self_type_id) -> SemIR::InstId {
CARBON_CHECK(self_type_id == SemIR::ErrorInst::TypeId ||
context.types().Is<SemIR::FacetType>(self_type_id));
CARBON_CHECK(context.types().IsFacetTypeOrError(self_type_id));
auto entity_name_id = context.entity_names().AddCanonical(
{.name_id = SemIR::NameId::PeriodSelf,
.parent_scope_id = context.scope_stack().PeekNameScopeId(),
@@ -57,8 +56,7 @@ static auto TryGetAsPeriodSelf(Context& context, SemIR::InstId inst_id,
return std::nullopt;
}
auto query_inst_id =
canonicalize ? GetCanonicalFacetOrTypeValue(context, const_inst_id)
: inst_id;
canonicalize ? GetCanonicalFacet(context, const_inst_id) : inst_id;
if (auto bind =
context.insts().TryGetAs<SemIR::SymbolicBinding>(query_inst_id)) {
const auto& entity_name = context.entity_names().Get(bind->entity_name_id);
@@ -122,7 +120,7 @@ class SubstPeriodSelfCallbacks : public SubstInstCallbacks {
context().constant_values().GetInstId(period_self_replacement_id_);
auto replacement_type_id =
context().insts().Get(replacement_self_inst_id).type_id();
CARBON_CHECK(context().types().IsFacetType(replacement_type_id));
CARBON_CHECK(context().types().Is<SemIR::FacetType>(replacement_type_id));
// If the replacement has the same type as `.Self`, use it directly.
if (replacement_type_id == period_self_type_id) {
@@ -136,42 +134,32 @@ class SubstPeriodSelfCallbacks : public SubstInstCallbacks {
}
// Convert the replacement facet to the type of `.Self`.
cached_replacement_id_ =
ConvertReplacement(replacement_self_inst_id, replacement_type_id,
period_self, period_self_type_id);
cached_replacement_id_ = ConvertReplacement(
replacement_self_inst_id, period_self, period_self_type_id);
cached_replacement_type_id_ = period_self_type_id;
return cached_replacement_id_;
}
auto ConvertReplacement(SemIR::InstId replacement_self_inst_id,
SemIR::TypeId replacement_type_id,
SemIR::InstId period_self_inst_id,
SemIR::TypeId period_self_type_id) -> SemIR::InstId {
// TODO: Replace all empty facet types with TypeType.
if (period_self_type_id == GetEmptyFacetType(context())) {
// Convert to an empty facet type (representing TypeType); we don't need
// any witnesses.
return ConvertToValueOfType(context(), loc_id_, replacement_self_inst_id,
period_self_type_id);
// Ensure the replacement is a type, which we will need for the return or to
// construct FacetValue.
auto replacement_self_type_inst_id = context().types().GetTypeInstId(
GetFacetAccessType(context(), replacement_self_inst_id));
if (period_self_type_id == SemIR::TypeType::TypeId) {
return replacement_self_type_inst_id;
}
// We have a facet or a type, but we need more interfaces in the facet type.
// We will have to synthesize a symbolic witness for each interface.
// We have a replacement facet (converted to `type`), but we need different
// interfaces than we had in the facet's type. We will have to synthesize a
// symbolic witness for each interface.
//
// Why is this okay? The type of `.Self` comes from interfaces that are
// before it (to the left of it) in the facet type. The replacement for
// `.Self` will have to impl those interfaces in order to match the facet
// type, so we know that it is valid to construct these witnesses.
// Make the replacement into a type, which we will need for the FacetValue.
if (context().types().Is<SemIR::FacetType>(replacement_type_id)) {
replacement_self_inst_id = context().constant_values().GetInstId(
EvalOrAddInst<SemIR::FacetAccessType>(
context(), loc_id_,
{.type_id = SemIR::TypeType::TypeId,
.facet_value_inst_id = replacement_self_inst_id}));
}
auto witnesses = MakeWitnessesForPeriodSelfTypeWithoutLookup(
context(), loc_id_,
context().constant_values().Get(replacement_self_inst_id),
@@ -184,8 +172,7 @@ class SubstPeriodSelfCallbacks : public SubstInstCallbacks {
context(), loc_id_,
{
.type_id = period_self_type_id,
.type_inst_id =
context().types().GetAsTypeInstId(replacement_self_inst_id),
.type_inst_id = replacement_self_type_inst_id,
.witnesses_block_id = witnesses.inst_block_id(),
}));
}
+1 -1
View File
@@ -72,7 +72,7 @@ auto SubstPeriodSelfInFacetType(Context& context, SemIR::LocId loc_id,
// Returns whether the constant value of `inst_id` is a reference to `.Self`.
//
// If `canonicalize` is true, look at the constant value of `inst_id` and get
// the canonicalized facet or type to look through FacetAccessType.
// the canonicalized facet to look through FacetAccessType.
auto IsPeriodSelf(Context& context, SemIR::InstId inst_id,
bool canonicalize = true) -> bool;
+4 -4
View File
@@ -143,7 +143,7 @@ auto ScopeStack::Pop(bool check_unused) -> void {
// TODO: Multiple diagnostics on same line has non-deterministic order.
// Add second sort key in diagnostics sorting.
scope.names.ForEach([&, check_unused](SemIR::NameId name_id) {
for (SemIR::NameId name_id : scope.names.entries()) {
auto& lexical_results = lexical_lookup_.Get(name_id);
CARBON_CHECK(lexical_results.back().scope_index == scope.index,
"Inconsistent scope index for name {0}", name_id);
@@ -151,7 +151,7 @@ auto ScopeStack::Pop(bool check_unused) -> void {
CheckUnusedBinding(*context_, name_id, lexical_results.back());
}
lexical_results.pop_back();
});
}
if (!scope.is_lexical_scope()) {
CARBON_CHECK(non_lexical_scope_stack_.back().scope_index == scope.index);
@@ -375,7 +375,7 @@ auto ScopeStack::Suspend() -> SuspendedScope {
result.suspended_items.reserve(result.entry.num_names +
peek_compile_time_bindings.size());
result.entry.names.ForEach([&](SemIR::NameId name_id) {
for (SemIR::NameId name_id : result.entry.names.entries()) {
auto suspended = lexical_lookup_.Suspend(name_id);
CARBON_CHECK(suspended.index !=
SuspendedScope::ScopeItem::IndexForCompileTimeBinding);
@@ -384,7 +384,7 @@ auto ScopeStack::Suspend() -> SuspendedScope {
.inst_id = suspended.inst_id,
.is_decl_reachable = suspended.is_decl_reachable,
.use_loc_id = suspended.use_loc_id});
});
}
CARBON_CHECK(static_cast<int>(result.suspended_items.size()) ==
result.entry.num_names);
+1 -1
View File
@@ -359,7 +359,7 @@ class ScopeStack {
// Names which are registered with lexical_lookup_, and will need to be
// unregistered when the scope ends.
Set<SemIR::NameId> names = {};
Set<SemIR::NameId, 16> names = {};
};
// A scope in which `return` can be used.
+3
View File
@@ -158,6 +158,7 @@ extern alias C = Class;
// CHECK:STDOUT: --- alias.carbon
// CHECK:STDOUT:
// CHECK:STDOUT: constants {
// CHECK:STDOUT: type: type = facet_type <type> [concrete]
// CHECK:STDOUT: %C: type = class_type @C [concrete]
// CHECK:STDOUT: }
// CHECK:STDOUT:
@@ -174,6 +175,7 @@ extern alias C = Class;
// CHECK:STDOUT: --- alias_of_alias.carbon
// CHECK:STDOUT:
// CHECK:STDOUT: constants {
// CHECK:STDOUT: type: type = facet_type <type> [concrete]
// CHECK:STDOUT: %C: type = class_type @C [concrete]
// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [concrete]
// CHECK:STDOUT: %pattern_type: type = pattern_type %C [concrete]
@@ -211,6 +213,7 @@ extern alias C = Class;
// CHECK:STDOUT: --- alias_to_generic.carbon
// CHECK:STDOUT:
// CHECK:STDOUT: constants {
// CHECK:STDOUT: type: type = facet_type <type> [concrete]
// CHECK:STDOUT: %A.type: type = generic_class_type @A [concrete]
// CHECK:STDOUT: %A.generic: %A.type = struct_value () [concrete]
// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [concrete]
+6
View File
@@ -39,6 +39,7 @@ let a_test: bool = a;
// CHECK:STDOUT: --- i32.carbon
// CHECK:STDOUT:
// CHECK:STDOUT: constants {
// CHECK:STDOUT: type: type = facet_type <type> [concrete]
// CHECK:STDOUT: %int_32: Core.IntLiteral = int_value 32 [concrete]
// CHECK:STDOUT: %i32: type = class_type @Int, @Int(%int_32) [concrete]
// CHECK:STDOUT: }
@@ -50,6 +51,10 @@ let a_test: bool = a;
// CHECK:STDOUT:
// CHECK:STDOUT: --- bool.carbon
// CHECK:STDOUT:
// CHECK:STDOUT: constants {
// CHECK:STDOUT: type: type = facet_type <type> [concrete]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: file {
// CHECK:STDOUT: %.loc5: type = type_literal bool [concrete = bool]
// CHECK:STDOUT: %b: type = alias_binding b, %.loc5 [concrete = bool]
@@ -58,6 +63,7 @@ let a_test: bool = a;
// CHECK:STDOUT: --- bool_value.carbon
// CHECK:STDOUT:
// CHECK:STDOUT: constants {
// CHECK:STDOUT: type: type = facet_type <type> [concrete]
// CHECK:STDOUT: %false: bool = bool_literal false [concrete]
// CHECK:STDOUT: %pattern_type: type = pattern_type bool [concrete]
// CHECK:STDOUT: %a_test.patt: %pattern_type = value_binding_pattern a_test [concrete]

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