mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 13:40:11 +01:00
Compare commits
39
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7cd39428e | ||
|
|
03939a9223 | ||
|
|
652e0ce7d0 | ||
|
|
c832d7c11c | ||
|
|
15e3eeaca9 | ||
|
|
efbe1d2489 | ||
|
|
53b7cfbaba | ||
|
|
795729bb4a | ||
|
|
fcae9610bd | ||
|
|
83ca5b71e3 | ||
|
|
fa12d9ded2 | ||
|
|
76e7fc5900 | ||
|
|
d037848a96 | ||
|
|
681b3b10c4 | ||
|
|
dbf79d5229 | ||
|
|
413ac55d4f | ||
|
|
804359dce0 | ||
|
|
d84387f8b8 | ||
|
|
094742740a | ||
|
|
994bad5143 | ||
|
|
ae4be1be14 | ||
|
|
e65694db57 | ||
|
|
5c601f8224 | ||
|
|
ccb5ba1b92 | ||
|
|
ebf1675356 | ||
|
|
843c7ce498 | ||
|
|
59c1d6ff39 | ||
|
|
eb887c367a | ||
|
|
f3d67de480 | ||
|
|
d8c4fc51cd | ||
|
|
64ce58dd64 | ||
|
|
437be71f1f | ||
|
|
bfebb7cb42 | ||
|
|
9e68ee0806 | ||
|
|
37949a2066 | ||
|
|
e962b12e53 | ||
|
|
4416f3525b | ||
|
|
db2e26ba86 | ||
|
|
cf66fb8aeb |
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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]
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -53,3 +53,7 @@ uv.lock
|
||||
|
||||
# Generated by scripts/create_compdb.py
|
||||
/external
|
||||
|
||||
# Linux perftools output
|
||||
perf.data
|
||||
perf.data.old
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
-576
@@ -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
@@ -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]+)?$$",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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>
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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;
|
||||
// }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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))"
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
|
||||
Executable
+112
@@ -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
@@ -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",
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
```
|
||||
|
||||
@@ -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
|
||||
@@ -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":
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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:
|
||||
|
||||
@@ -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_
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_;
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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) + "`";
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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(),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user