mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 13:40:11 +01:00
Compare commits
23
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 |
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
+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;
|
||||
// }
|
||||
|
||||
@@ -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,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
|
||||
+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 "$@"
|
||||
@@ -86,6 +86,7 @@ Example usage:
|
||||
"constant": "SemIR::MakeConstantId",
|
||||
"constraint": "SemIR::MakeNamedConstraintId",
|
||||
"declared_facet_type": "SemIR::MakeDeclaredFacetTypeId",
|
||||
"default_value": "SemIR::MakeDefaultValueId",
|
||||
"entity_name": "SemIR::MakeEntityNameId",
|
||||
"function": "SemIR::MakeFunctionId",
|
||||
"generated_function": "SemIR::MakeGeneratedFunctionId",
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+23
-10
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
// --------------------------------------------------------------------------
|
||||
@@ -399,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();
|
||||
}
|
||||
@@ -626,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+26
-31
@@ -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}",
|
||||
@@ -1556,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`.
|
||||
@@ -1574,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;
|
||||
@@ -1585,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()) {
|
||||
@@ -1608,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,
|
||||
@@ -1627,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;
|
||||
}
|
||||
@@ -1742,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} "
|
||||
@@ -2113,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;
|
||||
}
|
||||
|
||||
@@ -2388,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
|
||||
|
||||
@@ -293,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,
|
||||
@@ -322,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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -241,7 +241,7 @@ static auto BuildDefaultWitness(
|
||||
query_specific_interface, {fn_id});
|
||||
}
|
||||
|
||||
static auto BuildDestroyWitness(
|
||||
static auto BuildCppDestroyWitness(
|
||||
Context& context, SemIR::LocId loc_id,
|
||||
SemIR::ConstantId query_self_const_id,
|
||||
SemIR::SpecificInterface query_specific_interface) -> SemIR::InstId {
|
||||
@@ -264,8 +264,11 @@ 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, {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.
|
||||
@@ -610,8 +613,8 @@ auto LookupCppImpl(Context& context, SemIR::LocId loc_id,
|
||||
return BuildDefaultWitness(context, loc_id, query_self_const_id,
|
||||
query_specific_interface);
|
||||
case SemIR::CoreInterface::Destroy:
|
||||
return BuildDestroyWitness(context, loc_id, query_self_const_id,
|
||||
query_specific_interface);
|
||||
return BuildCppDestroyWitness(context, loc_id, query_self_const_id,
|
||||
query_specific_interface);
|
||||
|
||||
case SemIR::CoreInterface::CppRangeForIterate:
|
||||
return BuildCppRangeForIterateWitness(
|
||||
|
||||
@@ -362,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
|
||||
@@ -1982,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,
|
||||
|
||||
@@ -39,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:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,37 +16,24 @@
|
||||
#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));
|
||||
|
||||
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 context.types().GetTypeIdForTypeInstId(facet_or_type_id);
|
||||
}
|
||||
|
||||
// Make the CanonicalKey for a generated function `op_name_id` in the interface
|
||||
// `core_specific_interface`.
|
||||
static auto MakeGeneratedFunctionKey(
|
||||
@@ -242,13 +230,13 @@ static auto CanDestroyType(Context& context, SemIR::LocId loc_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;
|
||||
}
|
||||
@@ -360,7 +348,6 @@ static auto CanDestroyType(Context& context, SemIR::LocId loc_id,
|
||||
case SemIR::IntLiteralType::Kind:
|
||||
case SemIR::IntType::Kind:
|
||||
case SemIR::PointerType::Kind:
|
||||
case SemIR::TypeType::Kind:
|
||||
// Trivially destructible.
|
||||
return DestroyFormat::Trivial;
|
||||
|
||||
@@ -369,14 +356,14 @@ static auto CanDestroyType(Context& context, SemIR::LocId loc_id,
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
@@ -393,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{});
|
||||
@@ -404,15 +391,48 @@ 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::InterfaceId interface_id,
|
||||
DestroyFormat format) -> SemIR::InstId {
|
||||
SemIR::InterfaceId interface_id)
|
||||
-> SemIR::InstId {
|
||||
auto name_id = context.core_identifiers().AddNameId(CoreIdentifier::Op);
|
||||
|
||||
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,
|
||||
@@ -429,8 +449,8 @@ static auto MakeDestroyOpFunction(Context& context, SemIR::LocId loc_id,
|
||||
builtin_kind = SemIR::BuiltinFunctionKind::NoOp;
|
||||
break;
|
||||
case DestroyFormat::NonTrivial: {
|
||||
auto body_id = MakeDestroyOpBody(context, loc_id, self_type_id,
|
||||
function.self_param_id);
|
||||
auto body_id = MakeSubobjectDestroyOpBody(context, loc_id, self_type_id,
|
||||
function.self_param_id);
|
||||
function.body_block_ids.push_back(body_id);
|
||||
break;
|
||||
}
|
||||
@@ -444,7 +464,54 @@ static auto MakeDestroyOpFunction(Context& context, SemIR::LocId loc_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;
|
||||
}
|
||||
|
||||
@@ -481,7 +548,8 @@ static auto GetTypesForSelfFacet(
|
||||
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};
|
||||
}
|
||||
|
||||
@@ -663,7 +731,8 @@ auto BuildPrimitiveCopyWitness(
|
||||
Context& context, SemIR::LocId loc_id,
|
||||
SemIR::ConstantId query_self_const_id,
|
||||
SemIR::SpecificInterface query_specific_interface) -> SemIR::InstId {
|
||||
auto self_type_id = GetFacetAsType(context, query_self_const_id);
|
||||
auto self_type_id = GetFacetAccessType(
|
||||
context, context.constant_values().GetInstId(query_self_const_id));
|
||||
|
||||
auto op_id = MakeBuiltinOperatorFunction(
|
||||
context, loc_id, {self_type_id}, self_type_id, CoreIdentifier::Op,
|
||||
@@ -673,21 +742,49 @@ auto BuildPrimitiveCopyWitness(
|
||||
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::SpecificInterface query_specific_interface, DestroyFormat format)
|
||||
-> SemIR::InstId {
|
||||
CARBON_CHECK(format != DestroyFormat::NoDestroy);
|
||||
|
||||
auto self_type_id = GetFacetAsType(context, query_self_const_id);
|
||||
auto op_id =
|
||||
MakeDestroyOpFunction(context, loc_id, self_type_id,
|
||||
query_specific_interface.interface_id, format);
|
||||
return BuildCustomWitness(context, loc_id, query_self_const_id,
|
||||
query_specific_interface, {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
|
||||
@@ -708,16 +805,17 @@ static auto LookupDestroyWitness(
|
||||
return SemIR::InstId::None;
|
||||
}
|
||||
|
||||
return BuildDestroyWitness(context, loc_id, query_self_const_id,
|
||||
query_specific_interface, 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::SpecificInterface query_specific_interface) -> SemIR::InstId {
|
||||
return BuildDestroyWitness(context, loc_id, query_self_const_id,
|
||||
query_specific_interface, DestroyFormat::Trivial);
|
||||
return BuildCarbonDestroyWitness(context, loc_id, query_self_const_id,
|
||||
query_specific_interface,
|
||||
DestroyFormat::Trivial);
|
||||
}
|
||||
|
||||
static auto MakeIntFitsInWitness(
|
||||
@@ -740,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) ||
|
||||
@@ -836,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) ||
|
||||
|
||||
@@ -67,6 +67,17 @@ auto LookupCustomWitness(Context& context, SemIR::LocId loc_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();
|
||||
}
|
||||
|
||||
@@ -64,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"
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,6 @@ 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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(
|
||||
@@ -983,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));
|
||||
@@ -1122,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
|
||||
@@ -1171,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);
|
||||
|
||||
@@ -38,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"
|
||||
@@ -179,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();
|
||||
}
|
||||
@@ -267,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();
|
||||
}
|
||||
@@ -2306,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();
|
||||
}
|
||||
@@ -2316,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}),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2425,7 +2441,6 @@ static auto ImportFunctionDecl(
|
||||
{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,
|
||||
@@ -2571,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 =
|
||||
@@ -2621,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()) {
|
||||
@@ -2631,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);
|
||||
@@ -2656,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:
|
||||
@@ -2689,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;
|
||||
}
|
||||
@@ -4569,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));
|
||||
}
|
||||
@@ -4976,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;
|
||||
|
||||
|
||||
+24
-46
@@ -339,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: {
|
||||
@@ -693,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,
|
||||
@@ -904,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]
|
||||
|
||||
@@ -75,6 +75,7 @@ var d: D* = &c;
|
||||
// CHECK:STDOUT: --- base.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: %complete_type: <witness> = complete_type_witness %empty_struct_type [concrete]
|
||||
@@ -110,6 +111,7 @@ var d: D* = &c;
|
||||
// CHECK:STDOUT: --- export.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: %complete_type: <witness> = complete_type_witness %empty_struct_type [concrete]
|
||||
@@ -147,6 +149,7 @@ var d: D* = &c;
|
||||
// CHECK:STDOUT: --- export_orig.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: %complete_type: <witness> = complete_type_witness %empty_struct_type [concrete]
|
||||
@@ -184,6 +187,7 @@ var d: D* = &c;
|
||||
// CHECK:STDOUT: --- use_export.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: %complete_type: <witness> = complete_type_witness %empty_struct_type [concrete]
|
||||
@@ -240,6 +244,7 @@ var d: D* = &c;
|
||||
// CHECK:STDOUT: --- fail_orig_name_not_in_export.carbon
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: constants {
|
||||
// CHECK:STDOUT: type: type = facet_type <type> [concrete]
|
||||
// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [concrete]
|
||||
// CHECK:STDOUT: %empty_struct: %empty_struct_type = struct_value () [concrete]
|
||||
// CHECK:STDOUT: }
|
||||
@@ -280,6 +285,7 @@ var d: D* = &c;
|
||||
// CHECK:STDOUT: --- indirect_compat.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: %complete_type.357: <witness> = complete_type_witness %empty_struct_type [concrete]
|
||||
|
||||
@@ -68,6 +68,7 @@ var c: () = a_alias_alias;
|
||||
// CHECK:STDOUT: --- class1.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: %complete_type.357: <witness> = complete_type_witness %empty_struct_type [concrete]
|
||||
@@ -143,6 +144,7 @@ var c: () = a_alias_alias;
|
||||
// CHECK:STDOUT: --- class2.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: %complete_type.357: <witness> = complete_type_witness %empty_struct_type [concrete]
|
||||
@@ -224,6 +226,7 @@ var c: () = a_alias_alias;
|
||||
// CHECK:STDOUT: --- class3.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: %complete_type.357: <witness> = complete_type_witness %empty_struct_type [concrete]
|
||||
@@ -300,6 +303,7 @@ var c: () = a_alias_alias;
|
||||
// CHECK:STDOUT: --- var1.carbon
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: constants {
|
||||
// CHECK:STDOUT: type: type = facet_type <type> [concrete]
|
||||
// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete]
|
||||
// CHECK:STDOUT: %empty_tuple: %empty_tuple.type = tuple_value () [concrete]
|
||||
// CHECK:STDOUT: %pattern_type: type = pattern_type %empty_tuple.type [concrete]
|
||||
@@ -347,6 +351,7 @@ var c: () = a_alias_alias;
|
||||
// CHECK:STDOUT: --- var2.carbon
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: constants {
|
||||
// CHECK:STDOUT: type: type = facet_type <type> [concrete]
|
||||
// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete]
|
||||
// CHECK:STDOUT: %pattern_type: type = pattern_type %empty_tuple.type [concrete]
|
||||
// CHECK:STDOUT: %a.patt: %pattern_type = ref_binding_pattern a [concrete]
|
||||
@@ -402,6 +407,7 @@ var c: () = a_alias_alias;
|
||||
// CHECK:STDOUT: --- var3.carbon
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: constants {
|
||||
// CHECK:STDOUT: type: type = facet_type <type> [concrete]
|
||||
// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete]
|
||||
// CHECK:STDOUT: %empty_tuple: %empty_tuple.type = tuple_value () [concrete]
|
||||
// CHECK:STDOUT: %pattern_type: type = pattern_type %empty_tuple.type [concrete]
|
||||
|
||||
@@ -59,6 +59,7 @@ var inst: Test.A = {};
|
||||
// CHECK:STDOUT: --- def.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: %complete_type: <witness> = complete_type_witness %empty_struct_type [concrete]
|
||||
@@ -85,6 +86,7 @@ var inst: Test.A = {};
|
||||
// CHECK:STDOUT: --- def.impl.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: %complete_type: <witness> = complete_type_witness %empty_struct_type [concrete]
|
||||
@@ -138,6 +140,7 @@ var inst: Test.A = {};
|
||||
// CHECK:STDOUT: --- fail_local_def.carbon
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: constants {
|
||||
// CHECK:STDOUT: type: type = facet_type <type> [concrete]
|
||||
// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [concrete]
|
||||
// CHECK:STDOUT: %empty_struct: %empty_struct_type = struct_value () [concrete]
|
||||
// CHECK:STDOUT: }
|
||||
@@ -172,6 +175,7 @@ var inst: Test.A = {};
|
||||
// CHECK:STDOUT: --- fail_other_def.carbon
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: constants {
|
||||
// CHECK:STDOUT: type: type = facet_type <type> [concrete]
|
||||
// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [concrete]
|
||||
// CHECK:STDOUT: %empty_struct: %empty_struct_type = struct_value () [concrete]
|
||||
// CHECK:STDOUT: }
|
||||
|
||||
@@ -36,6 +36,7 @@ var a_val: a = {.v = b_val.v};
|
||||
// CHECK:STDOUT: --- a.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_tuple.type: type = tuple_type () [concrete]
|
||||
// CHECK:STDOUT: %empty_tuple: %empty_tuple.type = tuple_value () [concrete]
|
||||
@@ -79,6 +80,7 @@ var a_val: a = {.v = b_val.v};
|
||||
// CHECK:STDOUT: --- b.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_tuple.type: type = tuple_type () [concrete]
|
||||
// CHECK:STDOUT: %struct_type.v: type = struct_type {.v: %empty_tuple.type} [concrete]
|
||||
|
||||
@@ -49,6 +49,7 @@ fn F() -> {} {
|
||||
// CHECK:STDOUT: --- in_namespace.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:
|
||||
|
||||
+2
@@ -46,6 +46,7 @@ fn F() -> () {
|
||||
// CHECK:STDOUT: --- global.carbon
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: constants {
|
||||
// CHECK:STDOUT: type: type = facet_type <type> [concrete]
|
||||
// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete]
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
@@ -64,6 +65,7 @@ fn F() -> () {
|
||||
// CHECK:STDOUT: --- fail_local.carbon
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: constants {
|
||||
// CHECK:STDOUT: type: type = facet_type <type> [concrete]
|
||||
// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete]
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
|
||||
+330
-20
@@ -108,6 +108,7 @@ var a: array(1, 1);
|
||||
// CHECK:STDOUT: --- assign_var.carbon
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: constants {
|
||||
// CHECK:STDOUT: type: type = facet_type <type> [concrete]
|
||||
// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete]
|
||||
// CHECK:STDOUT: %empty_tuple: %empty_tuple.type = tuple_value () [concrete]
|
||||
// CHECK:STDOUT: %tuple.type: type = tuple_type (%empty_tuple.type, %empty_tuple.type, %empty_tuple.type) [concrete]
|
||||
@@ -171,11 +172,13 @@ var a: array(1, 1);
|
||||
// CHECK:STDOUT: --- array_in_place.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: %tuple.type.ff9: type = tuple_type (type, type, type) [concrete]
|
||||
// CHECK:STDOUT: %tuple: %tuple.type.ff9 = tuple_value (%C, %C, %C) [concrete]
|
||||
// CHECK:STDOUT: %tuple.type.531: type = tuple_type (type, type, type) [concrete]
|
||||
// CHECK:STDOUT: %tuple: %tuple.type.531 = tuple_value (%C, %C, %C) [concrete]
|
||||
// CHECK:STDOUT: %tuple.type.a8c: type = tuple_type (%C, %C, %C) [concrete]
|
||||
// CHECK:STDOUT: %pattern_type.730: type = pattern_type %tuple.type.a8c [concrete]
|
||||
// CHECK:STDOUT: %F.type: type = fn_type @F [concrete]
|
||||
// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete]
|
||||
// CHECK:STDOUT: %F: %F.type = struct_value () [concrete]
|
||||
@@ -187,8 +190,93 @@ var a: array(1, 1);
|
||||
// CHECK:STDOUT: %tuple.type.8c7: type = tuple_type (%tuple.type.a8c, %tuple.type.a8c) [concrete]
|
||||
// CHECK:STDOUT: %int_0: Core.IntLiteral = int_value 0 [concrete]
|
||||
// CHECK:STDOUT: %int_1: Core.IntLiteral = int_value 1 [concrete]
|
||||
// CHECK:STDOUT: %pattern_type.a96: type = pattern_type %empty_struct_type [concrete]
|
||||
// CHECK:STDOUT: %self.param_patt.52f: %pattern_type.a96 = ref_param_pattern [concrete]
|
||||
// CHECK:STDOUT: %self.patt.4b1: %pattern_type.a96 = wrapper_binding_pattern self, %self.param_patt.52f [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.1: type = fn_type @Destroy.WithSelf.SubobjectDestroy.loc10_3.1 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.d01daf.1: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.1 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.type.ef016f.1: type = fn_type @Destroy.WithSelf.Op.loc10_3.1 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.403171.1: %Destroy.WithSelf.Op.type.ef016f.1 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %pattern_type.98b: type = pattern_type %C [concrete]
|
||||
// CHECK:STDOUT: %self.param_patt.99a: %pattern_type.98b = ref_param_pattern [concrete]
|
||||
// CHECK:STDOUT: %self.patt.cbd: %pattern_type.98b = wrapper_binding_pattern self, %self.param_patt.99a [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.2: type = fn_type @Destroy.WithSelf.SubobjectDestroy.loc10_3.2 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.d01daf.2: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.2 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.type.ef016f.2: type = fn_type @Destroy.WithSelf.Op.loc10_3.2 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.403171.2: %Destroy.WithSelf.Op.type.ef016f.2 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %self.param_patt.bed: %pattern_type.730 = ref_param_pattern [concrete]
|
||||
// CHECK:STDOUT: %self.patt.d67: %pattern_type.730 = wrapper_binding_pattern self, %self.param_patt.bed [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.3: type = fn_type @Destroy.WithSelf.SubobjectDestroy.loc10_3.3 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.d01daf.3: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.3 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.type.ef016f.3: type = fn_type @Destroy.WithSelf.Op.loc10_3.3 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.403171.3: %Destroy.WithSelf.Op.type.ef016f.3 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %self.param_patt.e5f: %pattern_type.c3e = ref_param_pattern [concrete]
|
||||
// CHECK:STDOUT: %self.patt.1ad: %pattern_type.c3e = wrapper_binding_pattern self, %self.param_patt.e5f [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.4: type = fn_type @Destroy.WithSelf.SubobjectDestroy.loc10_3.4 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.d01daf.4: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.4 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.type.ef016f.4: type = fn_type @Destroy.WithSelf.Op.loc10_3.4 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.403171.4: %Destroy.WithSelf.Op.type.ef016f.4 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.type.fbceb5.4: type = fn_type @Destroy.WithSelf.SelfDestruct.loc10_3.4 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.db3fdb.4: %Destroy.WithSelf.SelfDestruct.type.fbceb5.4 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: generated {
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.decl.763c71.1: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.1 = fn_decl @Destroy.WithSelf.SubobjectDestroy.loc10_3.1 [concrete = constants.%Destroy.WithSelf.SubobjectDestroy.d01daf.1] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.a96 = ref_param_pattern [concrete = constants.%self.param_patt.52f]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.a96 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.4b1]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %empty_struct_type = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %empty_struct_type = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.decl.5f94cf.1: %Destroy.WithSelf.Op.type.ef016f.1 = fn_decl @Destroy.WithSelf.Op.loc10_3.1 [concrete = constants.%Destroy.WithSelf.Op.403171.1] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.a96 = ref_param_pattern [concrete = constants.%self.param_patt.52f]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.a96 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.4b1]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %empty_struct_type = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %empty_struct_type = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.decl.763c71.2: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.2 = fn_decl @Destroy.WithSelf.SubobjectDestroy.loc10_3.2 [concrete = constants.%Destroy.WithSelf.SubobjectDestroy.d01daf.2] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.98b = ref_param_pattern [concrete = constants.%self.param_patt.99a]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.98b = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.cbd]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %C = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %C = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.decl.5f94cf.2: %Destroy.WithSelf.Op.type.ef016f.2 = fn_decl @Destroy.WithSelf.Op.loc10_3.2 [concrete = constants.%Destroy.WithSelf.Op.403171.2] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.98b = ref_param_pattern [concrete = constants.%self.param_patt.99a]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.98b = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.cbd]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %C = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %C = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.decl.763c71.3: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.3 = fn_decl @Destroy.WithSelf.SubobjectDestroy.loc10_3.3 [concrete = constants.%Destroy.WithSelf.SubobjectDestroy.d01daf.3] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.730 = ref_param_pattern [concrete = constants.%self.param_patt.bed]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.730 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.d67]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %tuple.type.a8c = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %tuple.type.a8c = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.decl.5f94cf.3: %Destroy.WithSelf.Op.type.ef016f.3 = fn_decl @Destroy.WithSelf.Op.loc10_3.3 [concrete = constants.%Destroy.WithSelf.Op.403171.3] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.730 = ref_param_pattern [concrete = constants.%self.param_patt.bed]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.730 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.d67]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %tuple.type.a8c = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %tuple.type.a8c = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.decl.763c71.4: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.4 = fn_decl @Destroy.WithSelf.SubobjectDestroy.loc10_3.4 [concrete = constants.%Destroy.WithSelf.SubobjectDestroy.d01daf.4] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.c3e = ref_param_pattern [concrete = constants.%self.param_patt.e5f]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.c3e = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.1ad]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %array_type = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %array_type = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.decl.5f94cf.4: %Destroy.WithSelf.Op.type.ef016f.4 = fn_decl @Destroy.WithSelf.Op.loc10_3.4 [concrete = constants.%Destroy.WithSelf.Op.403171.4] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.c3e = ref_param_pattern [concrete = constants.%self.param_patt.e5f]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.c3e = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.1ad]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %array_type = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %array_type = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @G() {
|
||||
@@ -214,7 +302,7 @@ var a: array(1, 1);
|
||||
// CHECK:STDOUT: %C.ref.loc10_24: type = name_ref C, file.%C.decl [concrete = constants.%C]
|
||||
// CHECK:STDOUT: %C.ref.loc10_27: type = name_ref C, file.%C.decl [concrete = constants.%C]
|
||||
// CHECK:STDOUT: %C.ref.loc10_30: type = name_ref C, file.%C.decl [concrete = constants.%C]
|
||||
// CHECK:STDOUT: %.loc10_31.1: %tuple.type.ff9 = tuple_literal (%C.ref.loc10_24, %C.ref.loc10_27, %C.ref.loc10_30) [concrete = constants.%tuple]
|
||||
// CHECK:STDOUT: %.loc10_31.1: %tuple.type.531 = tuple_literal (%C.ref.loc10_24, %C.ref.loc10_27, %C.ref.loc10_30) [concrete = constants.%tuple]
|
||||
// CHECK:STDOUT: %int_2: Core.IntLiteral = int_value 2 [concrete = constants.%int_2]
|
||||
// CHECK:STDOUT: %.loc10_31.2: type = converted %.loc10_31.1, constants.%tuple.type.a8c [concrete = constants.%tuple.type.a8c]
|
||||
// CHECK:STDOUT: %array_type: type = array_type %int_2, %.loc10_31.2 [concrete = constants.%array_type]
|
||||
@@ -224,31 +312,68 @@ var a: array(1, 1);
|
||||
// CHECK:STDOUT: %v.patt: %pattern_type.c3e = ref_binding_pattern v [concrete = constants.%v.patt]
|
||||
// CHECK:STDOUT: %v.var_patt: %pattern_type.c3e = var_pattern %v.patt [concrete = constants.%v.var_patt]
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.bound: <bound method> = bound_method %v.var, constants.%Destroy.WithSelf.Op.403171.4
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call %Destroy.WithSelf.Op.bound(%v.var)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.bound: <bound method> = bound_method %v.var, constants.%Destroy.WithSelf.SelfDestruct.db3fdb.4
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.call: init %empty_tuple.type = call %Destroy.WithSelf.SelfDestruct.bound(%v.var)
|
||||
// CHECK:STDOUT: <elided>
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SubobjectDestroy.loc10_3.1(%self.param: ref %empty_struct_type) = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc10_3.1(%self.param: ref %empty_struct_type) = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc10_3.2(%self.param: ref %C) {
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SelfDestruct.loc10_3.1(%self.param: ref %empty_struct_type) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.Op.decl.5f94cf.1(%self.param)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.SubobjectDestroy.decl.763c71.1(%self.param)
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SubobjectDestroy.loc10_3.2(%self.param: ref %C) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc10_3.3(%self.param: ref %tuple.type.a8c) {
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc10_3.2(%self.param: ref %C) = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SelfDestruct.loc10_3.2(%self.param: ref %C) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.Op.decl.5f94cf.2(%self.param)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.SubobjectDestroy.decl.763c71.2(%self.param)
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SubobjectDestroy.loc10_3.3(%self.param: ref %tuple.type.a8c) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc10_3.4(%self.param: ref %array_type) {
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc10_3.3(%self.param: ref %tuple.type.a8c) = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SelfDestruct.loc10_3.3(%self.param: ref %tuple.type.a8c) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.Op.decl.5f94cf.3(%self.param)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.SubobjectDestroy.decl.763c71.3(%self.param)
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SubobjectDestroy.loc10_3.4(%self.param: ref %array_type) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc10_3.4(%self.param: ref %array_type) = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SelfDestruct.loc10_3.4(%self.param: ref %array_type) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.Op.decl.5f94cf.4(%self.param)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.SubobjectDestroy.decl.763c71.4(%self.param)
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: --- array_vs_tuple.carbon
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: constants {
|
||||
// CHECK:STDOUT: type: type = facet_type <type> [concrete]
|
||||
// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete]
|
||||
// CHECK:STDOUT: %empty_tuple: %empty_tuple.type = tuple_value () [concrete]
|
||||
// CHECK:STDOUT: %int_3: Core.IntLiteral = int_value 3 [concrete]
|
||||
@@ -269,10 +394,29 @@ var a: array(1, 1);
|
||||
// CHECK:STDOUT: %b.var_patt: %pattern_type.8c1 = var_pattern %b.patt [concrete]
|
||||
// CHECK:STDOUT: %DefaultOrUnformed.impl_witness.b33: <witness> = impl_witness imports.%DefaultOrUnformed.impl_witness_table.856, @T.as.DefaultOrUnformed.impl(%tuple.type) [concrete]
|
||||
// CHECK:STDOUT: %DefaultOrUnformed.facet.f59: %DefaultOrUnformed.type = facet_value %tuple.type, (%DefaultOrUnformed.impl_witness.b33) [concrete]
|
||||
// CHECK:STDOUT: %pattern_type.cb1: type = pattern_type %empty_tuple.type [concrete]
|
||||
// CHECK:STDOUT: %self.param_patt.65a: %pattern_type.cb1 = ref_param_pattern [concrete]
|
||||
// CHECK:STDOUT: %self.patt.df1: %pattern_type.cb1 = wrapper_binding_pattern self, %self.param_patt.65a [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.1: type = fn_type @Destroy.WithSelf.SubobjectDestroy.loc8_3.1 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.d01daf.1: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.1 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.type.ef016f.1: type = fn_type @Destroy.WithSelf.Op.loc8_3.1 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.403171.1: %Destroy.WithSelf.Op.type.ef016f.1 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %self.param_patt.327: %pattern_type.8c1 = ref_param_pattern [concrete]
|
||||
// CHECK:STDOUT: %self.patt.570: %pattern_type.8c1 = wrapper_binding_pattern self, %self.param_patt.327 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.2: type = fn_type @Destroy.WithSelf.SubobjectDestroy.loc8_3.2 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.d01daf.2: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.2 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.type.ef016f.2: type = fn_type @Destroy.WithSelf.Op.loc8_3.2 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.403171.2: %Destroy.WithSelf.Op.type.ef016f.2 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.type.fbceb5.2: type = fn_type @Destroy.WithSelf.SelfDestruct.loc8_3.2 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.db3fdb.2: %Destroy.WithSelf.SelfDestruct.type.fbceb5.2 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %self.param_patt.279: %pattern_type.035 = ref_param_pattern [concrete]
|
||||
// CHECK:STDOUT: %self.patt.06d: %pattern_type.035 = wrapper_binding_pattern self, %self.param_patt.279 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.3: type = fn_type @Destroy.WithSelf.SubobjectDestroy.loc7 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.d01daf.3: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.3 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.type.ef016f.3: type = fn_type @Destroy.WithSelf.Op.loc7 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.403171.3: %Destroy.WithSelf.Op.type.ef016f.3 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.type.fbceb5.3: type = fn_type @Destroy.WithSelf.SelfDestruct.loc7 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.db3fdb.3: %Destroy.WithSelf.SelfDestruct.type.fbceb5.3 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: imports {
|
||||
@@ -280,6 +424,51 @@ var a: array(1, 1);
|
||||
// CHECK:STDOUT: %DefaultOrUnformed.impl_witness_table.856 = impl_witness_table (%Core.import_ref.cc8), @T.as.DefaultOrUnformed.impl [concrete]
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: generated {
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.decl.763c71.1: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.1 = fn_decl @Destroy.WithSelf.SubobjectDestroy.loc8_3.1 [concrete = constants.%Destroy.WithSelf.SubobjectDestroy.d01daf.1] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.cb1 = ref_param_pattern [concrete = constants.%self.param_patt.65a]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.cb1 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.df1]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %empty_tuple.type = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %empty_tuple.type = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.decl.5f94cf.1: %Destroy.WithSelf.Op.type.ef016f.1 = fn_decl @Destroy.WithSelf.Op.loc8_3.1 [concrete = constants.%Destroy.WithSelf.Op.403171.1] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.cb1 = ref_param_pattern [concrete = constants.%self.param_patt.65a]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.cb1 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.df1]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %empty_tuple.type = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %empty_tuple.type = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.decl.763c71.2: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.2 = fn_decl @Destroy.WithSelf.SubobjectDestroy.loc8_3.2 [concrete = constants.%Destroy.WithSelf.SubobjectDestroy.d01daf.2] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.8c1 = ref_param_pattern [concrete = constants.%self.param_patt.327]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.8c1 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.570]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %tuple.type = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %tuple.type = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.decl.5f94cf.2: %Destroy.WithSelf.Op.type.ef016f.2 = fn_decl @Destroy.WithSelf.Op.loc8_3.2 [concrete = constants.%Destroy.WithSelf.Op.403171.2] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.8c1 = ref_param_pattern [concrete = constants.%self.param_patt.327]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.8c1 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.570]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %tuple.type = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %tuple.type = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.decl.763c71.3: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.3 = fn_decl @Destroy.WithSelf.SubobjectDestroy.loc7 [concrete = constants.%Destroy.WithSelf.SubobjectDestroy.d01daf.3] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.035 = ref_param_pattern [concrete = constants.%self.param_patt.279]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.035 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.06d]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %array_type = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %array_type = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.decl.5f94cf.3: %Destroy.WithSelf.Op.type.ef016f.3 = fn_decl @Destroy.WithSelf.Op.loc7 [concrete = constants.%Destroy.WithSelf.Op.403171.3] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.035 = ref_param_pattern [concrete = constants.%self.param_patt.279]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.035 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.06d]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %array_type = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %array_type = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @G() {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: %a.var: ref %array_type = var_storage %a.var_patt
|
||||
@@ -326,31 +515,60 @@ var a: array(1, 1);
|
||||
// CHECK:STDOUT: %b.patt: %pattern_type.8c1 = ref_binding_pattern b [concrete = constants.%b.patt]
|
||||
// CHECK:STDOUT: %b.var_patt: %pattern_type.8c1 = var_pattern %b.patt [concrete = constants.%b.var_patt]
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.bound.loc8: <bound method> = bound_method %b.var, constants.%Destroy.WithSelf.Op.403171.2
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call.loc8: init %empty_tuple.type = call %Destroy.WithSelf.Op.bound.loc8(%b.var)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.bound.loc7: <bound method> = bound_method %a.var, constants.%Destroy.WithSelf.Op.403171.3
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call.loc7: init %empty_tuple.type = call %Destroy.WithSelf.Op.bound.loc7(%a.var)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.bound.loc8: <bound method> = bound_method %b.var, constants.%Destroy.WithSelf.SelfDestruct.db3fdb.2
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.call.loc8: init %empty_tuple.type = call %Destroy.WithSelf.SelfDestruct.bound.loc8(%b.var)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.bound.loc7: <bound method> = bound_method %a.var, constants.%Destroy.WithSelf.SelfDestruct.db3fdb.3
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.call.loc7: init %empty_tuple.type = call %Destroy.WithSelf.SelfDestruct.bound.loc7(%a.var)
|
||||
// CHECK:STDOUT: <elided>
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SubobjectDestroy.loc8_3.1(%self.param: ref %empty_tuple.type) = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc8_3.1(%self.param: ref %empty_tuple.type) = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc8_3.2(%self.param: ref %tuple.type) {
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SelfDestruct.loc8_3.1(%self.param: ref %empty_tuple.type) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.Op.decl.5f94cf.1(%self.param)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.SubobjectDestroy.decl.763c71.1(%self.param)
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SubobjectDestroy.loc8_3.2(%self.param: ref %tuple.type) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc7(%self.param: ref %array_type) {
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc8_3.2(%self.param: ref %tuple.type) = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SelfDestruct.loc8_3.2(%self.param: ref %tuple.type) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.Op.decl.5f94cf.2(%self.param)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.SubobjectDestroy.decl.763c71.2(%self.param)
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SubobjectDestroy.loc7(%self.param: ref %array_type) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc7(%self.param: ref %array_type) = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SelfDestruct.loc7(%self.param: ref %array_type) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.Op.decl.5f94cf.3(%self.param)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.SubobjectDestroy.decl.763c71.3(%self.param)
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: --- assign_return_value.carbon
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: constants {
|
||||
// CHECK:STDOUT: type: type = facet_type <type> [concrete]
|
||||
// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete]
|
||||
// CHECK:STDOUT: %empty_tuple: %empty_tuple.type = tuple_value () [concrete]
|
||||
// CHECK:STDOUT: %tuple.type: type = tuple_type (%empty_tuple.type) [concrete]
|
||||
// CHECK:STDOUT: %pattern_type.559: type = pattern_type %tuple.type [concrete]
|
||||
// CHECK:STDOUT: %F.type: type = fn_type @F [concrete]
|
||||
// CHECK:STDOUT: %F: %F.type = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %int_1: Core.IntLiteral = int_value 1 [concrete]
|
||||
@@ -360,10 +578,74 @@ var a: array(1, 1);
|
||||
// CHECK:STDOUT: %t.var_patt: %pattern_type.fe8 = var_pattern %t.patt [concrete]
|
||||
// CHECK:STDOUT: %int_0: Core.IntLiteral = int_value 0 [concrete]
|
||||
// CHECK:STDOUT: %array: %array_type = tuple_value (%empty_tuple) [concrete]
|
||||
// CHECK:STDOUT: %pattern_type.cb1: type = pattern_type %empty_tuple.type [concrete]
|
||||
// CHECK:STDOUT: %self.param_patt.65a: %pattern_type.cb1 = ref_param_pattern [concrete]
|
||||
// CHECK:STDOUT: %self.patt.df1: %pattern_type.cb1 = wrapper_binding_pattern self, %self.param_patt.65a [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.1: type = fn_type @Destroy.WithSelf.SubobjectDestroy.loc8_34.1 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.d01daf.1: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.1 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.type.ef016f.1: type = fn_type @Destroy.WithSelf.Op.loc8_34.1 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.403171.1: %Destroy.WithSelf.Op.type.ef016f.1 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %self.param_patt.383: %pattern_type.559 = ref_param_pattern [concrete]
|
||||
// CHECK:STDOUT: %self.patt.363: %pattern_type.559 = wrapper_binding_pattern self, %self.param_patt.383 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.2: type = fn_type @Destroy.WithSelf.SubobjectDestroy.loc8_34.2 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.d01daf.2: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.2 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.type.ef016f.2: type = fn_type @Destroy.WithSelf.Op.loc8_34.2 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.403171.2: %Destroy.WithSelf.Op.type.ef016f.2 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.type.fbceb5.2: type = fn_type @Destroy.WithSelf.SelfDestruct.loc8_34.2 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.db3fdb.2: %Destroy.WithSelf.SelfDestruct.type.fbceb5.2 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %self.param_patt.f73: %pattern_type.fe8 = ref_param_pattern [concrete]
|
||||
// CHECK:STDOUT: %self.patt.741: %pattern_type.fe8 = wrapper_binding_pattern self, %self.param_patt.f73 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.3: type = fn_type @Destroy.WithSelf.SubobjectDestroy.loc8_3 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.d01daf.3: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.3 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.type.ef016f.3: type = fn_type @Destroy.WithSelf.Op.loc8_3 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.403171.3: %Destroy.WithSelf.Op.type.ef016f.3 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.type.fbceb5.3: type = fn_type @Destroy.WithSelf.SelfDestruct.loc8_3 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.db3fdb.3: %Destroy.WithSelf.SelfDestruct.type.fbceb5.3 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: generated {
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.decl.763c71.1: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.1 = fn_decl @Destroy.WithSelf.SubobjectDestroy.loc8_34.1 [concrete = constants.%Destroy.WithSelf.SubobjectDestroy.d01daf.1] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.cb1 = ref_param_pattern [concrete = constants.%self.param_patt.65a]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.cb1 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.df1]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %empty_tuple.type = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %empty_tuple.type = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.decl.5f94cf.1: %Destroy.WithSelf.Op.type.ef016f.1 = fn_decl @Destroy.WithSelf.Op.loc8_34.1 [concrete = constants.%Destroy.WithSelf.Op.403171.1] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.cb1 = ref_param_pattern [concrete = constants.%self.param_patt.65a]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.cb1 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.df1]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %empty_tuple.type = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %empty_tuple.type = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.decl.763c71.2: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.2 = fn_decl @Destroy.WithSelf.SubobjectDestroy.loc8_34.2 [concrete = constants.%Destroy.WithSelf.SubobjectDestroy.d01daf.2] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.559 = ref_param_pattern [concrete = constants.%self.param_patt.383]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.559 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.363]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %tuple.type = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %tuple.type = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.decl.5f94cf.2: %Destroy.WithSelf.Op.type.ef016f.2 = fn_decl @Destroy.WithSelf.Op.loc8_34.2 [concrete = constants.%Destroy.WithSelf.Op.403171.2] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.559 = ref_param_pattern [concrete = constants.%self.param_patt.383]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.559 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.363]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %tuple.type = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %tuple.type = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.decl.763c71.3: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.3 = fn_decl @Destroy.WithSelf.SubobjectDestroy.loc8_3 [concrete = constants.%Destroy.WithSelf.SubobjectDestroy.d01daf.3] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.fe8 = ref_param_pattern [concrete = constants.%self.param_patt.f73]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.fe8 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.741]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %array_type = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %array_type = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.decl.5f94cf.3: %Destroy.WithSelf.Op.type.ef016f.3 = fn_decl @Destroy.WithSelf.Op.loc8_3 [concrete = constants.%Destroy.WithSelf.Op.403171.3] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.fe8 = ref_param_pattern [concrete = constants.%self.param_patt.f73]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.fe8 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.741]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %array_type = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %array_type = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Run() {
|
||||
@@ -393,28 +675,56 @@ var a: array(1, 1);
|
||||
// CHECK:STDOUT: %t.patt: %pattern_type.fe8 = ref_binding_pattern t [concrete = constants.%t.patt]
|
||||
// CHECK:STDOUT: %t.var_patt: %pattern_type.fe8 = var_pattern %t.patt [concrete = constants.%t.var_patt]
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.bound.loc8_34: <bound method> = bound_method %.loc8_34.2, constants.%Destroy.WithSelf.Op.403171.2
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call.loc8_34: init %empty_tuple.type = call %Destroy.WithSelf.Op.bound.loc8_34(%.loc8_34.2)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.bound.loc8_3: <bound method> = bound_method %t.var, constants.%Destroy.WithSelf.Op.403171.3
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call.loc8_3: init %empty_tuple.type = call %Destroy.WithSelf.Op.bound.loc8_3(%t.var)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.bound.loc8_34: <bound method> = bound_method %.loc8_34.2, constants.%Destroy.WithSelf.SelfDestruct.db3fdb.2
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.call.loc8_34: init %empty_tuple.type = call %Destroy.WithSelf.SelfDestruct.bound.loc8_34(%.loc8_34.2)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.bound.loc8_3: <bound method> = bound_method %t.var, constants.%Destroy.WithSelf.SelfDestruct.db3fdb.3
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.call.loc8_3: init %empty_tuple.type = call %Destroy.WithSelf.SelfDestruct.bound.loc8_3(%t.var)
|
||||
// CHECK:STDOUT: <elided>
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SubobjectDestroy.loc8_34.1(%self.param: ref %empty_tuple.type) = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc8_34.1(%self.param: ref %empty_tuple.type) = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc8_34.2(%self.param: ref %tuple.type) {
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SelfDestruct.loc8_34.1(%self.param: ref %empty_tuple.type) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.Op.decl.5f94cf.1(%self.param)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.SubobjectDestroy.decl.763c71.1(%self.param)
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SubobjectDestroy.loc8_34.2(%self.param: ref %tuple.type) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc8_3(%self.param: ref %array_type) {
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc8_34.2(%self.param: ref %tuple.type) = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SelfDestruct.loc8_34.2(%self.param: ref %tuple.type) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.Op.decl.5f94cf.2(%self.param)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.SubobjectDestroy.decl.763c71.2(%self.param)
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SubobjectDestroy.loc8_3(%self.param: ref %array_type) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc8_3(%self.param: ref %array_type) = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SelfDestruct.loc8_3(%self.param: ref %array_type) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.Op.decl.5f94cf.3(%self.param)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.SubobjectDestroy.decl.763c71.3(%self.param)
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: --- nine_elements.carbon
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: constants {
|
||||
// CHECK:STDOUT: type: type = facet_type <type> [concrete]
|
||||
// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete]
|
||||
// CHECK:STDOUT: %empty_tuple: %empty_tuple.type = tuple_value () [concrete]
|
||||
// CHECK:STDOUT: %tuple.type: type = tuple_type (%empty_tuple.type, %empty_tuple.type, %empty_tuple.type, %empty_tuple.type, %empty_tuple.type, %empty_tuple.type, %empty_tuple.type, %empty_tuple.type, %empty_tuple.type) [concrete]
|
||||
|
||||
@@ -68,6 +68,7 @@ var b: array(1, 39999999999999999993);
|
||||
// CHECK:STDOUT: --- addition.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: %int_1.5b8: Core.IntLiteral = int_value 1 [concrete]
|
||||
@@ -112,6 +113,7 @@ var b: array(1, 39999999999999999993);
|
||||
// CHECK:STDOUT: --- unsigned.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: %int_3.1ba: Core.IntLiteral = int_value 3 [concrete]
|
||||
|
||||
+99
-4
@@ -53,15 +53,18 @@ fn F() -> i32 {
|
||||
// CHECK:STDOUT: --- user.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: %empty_tuple.type: type = tuple_type () [concrete]
|
||||
// CHECK:STDOUT: %N: Core.IntLiteral = symbolic_binding N, 0 [symbolic]
|
||||
// CHECK:STDOUT: %i32: type = class_type @Int, @Int(%int_32) [concrete]
|
||||
// CHECK:STDOUT: %pattern_type.6b6: type = pattern_type %i32 [concrete]
|
||||
// CHECK:STDOUT: %i32.builtin: type = int_type signed, %int_32 [concrete]
|
||||
// CHECK:STDOUT: %F.type: type = fn_type @F [concrete]
|
||||
// CHECK:STDOUT: %F: %F.type = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %int_42: Core.IntLiteral = int_value 42 [concrete]
|
||||
// CHECK:STDOUT: %array_type: type = array_type %int_42, %i32 [concrete]
|
||||
// CHECK:STDOUT: %pattern_type.c07: type = pattern_type %array_type [concrete]
|
||||
// CHECK:STDOUT: %Copy.type: type = facet_type <@Copy> [concrete]
|
||||
// CHECK:STDOUT: %Int.as.Copy.impl.Op.type.ac8: type = fn_type @Int.as.Copy.impl.Op, @Int.as.Copy.impl(%N) [symbolic]
|
||||
// CHECK:STDOUT: %Int.as.Copy.impl.Op.5e0: %Int.as.Copy.impl.Op.type.ac8 = struct_value () [symbolic]
|
||||
@@ -72,8 +75,27 @@ fn F() -> i32 {
|
||||
// CHECK:STDOUT: %Copy.WithSelf.Op.type.381: type = fn_type @Copy.WithSelf.Op, @Copy.WithSelf(%Copy.facet) [concrete]
|
||||
// CHECK:STDOUT: %.737: type = fn_type_with_self_type %Copy.WithSelf.Op.type.381, %Copy.facet [concrete]
|
||||
// CHECK:STDOUT: %Int.as.Copy.impl.Op.specific_fn: <specific function> = specific_function %Int.as.Copy.impl.Op.4f6, @Int.as.Copy.impl.Op(%int_32) [concrete]
|
||||
// CHECK:STDOUT: %pattern_type.956: type = pattern_type %i32.builtin [concrete]
|
||||
// CHECK:STDOUT: %self.param_patt.331: %pattern_type.956 = ref_param_pattern [concrete]
|
||||
// CHECK:STDOUT: %self.patt.319: %pattern_type.956 = wrapper_binding_pattern self, %self.param_patt.331 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.1: type = fn_type @Destroy.WithSelf.SubobjectDestroy.loc6_12.1 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.d01daf.1: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.1 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.type.ef016f.1: type = fn_type @Destroy.WithSelf.Op.loc6_12.1 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.403171.1: %Destroy.WithSelf.Op.type.ef016f.1 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %self.param_patt.705: %pattern_type.6b6 = ref_param_pattern [concrete]
|
||||
// CHECK:STDOUT: %self.patt.70d: %pattern_type.6b6 = wrapper_binding_pattern self, %self.param_patt.705 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.2: type = fn_type @Destroy.WithSelf.SubobjectDestroy.loc6_12.2 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.d01daf.2: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.2 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.type.ef016f.2: type = fn_type @Destroy.WithSelf.Op.loc6_12.2 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.403171.2: %Destroy.WithSelf.Op.type.ef016f.2 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %self.param_patt.087: %pattern_type.c07 = ref_param_pattern [concrete]
|
||||
// CHECK:STDOUT: %self.patt.b22: %pattern_type.c07 = wrapper_binding_pattern self, %self.param_patt.087 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.3: type = fn_type @Destroy.WithSelf.SubobjectDestroy.loc6_12.3 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.d01daf.3: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.3 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.type.ef016f.3: type = fn_type @Destroy.WithSelf.Op.loc6_12.3 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.403171.3: %Destroy.WithSelf.Op.type.ef016f.3 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.type.fbceb5.3: type = fn_type @Destroy.WithSelf.SelfDestruct.loc6_12.3 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.db3fdb.3: %Destroy.WithSelf.SelfDestruct.type.fbceb5.3 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: imports {
|
||||
@@ -82,6 +104,51 @@ fn F() -> i32 {
|
||||
// CHECK:STDOUT: %Copy.impl_witness_table.8d2 = impl_witness_table (%Core.import_ref.cd6), @Int.as.Copy.impl [concrete]
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: generated {
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.decl.763c71.1: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.1 = fn_decl @Destroy.WithSelf.SubobjectDestroy.loc6_12.1 [concrete = constants.%Destroy.WithSelf.SubobjectDestroy.d01daf.1] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.956 = ref_param_pattern [concrete = constants.%self.param_patt.331]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.956 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.319]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %i32.builtin = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %i32.builtin = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.decl.5f94cf.1: %Destroy.WithSelf.Op.type.ef016f.1 = fn_decl @Destroy.WithSelf.Op.loc6_12.1 [concrete = constants.%Destroy.WithSelf.Op.403171.1] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.956 = ref_param_pattern [concrete = constants.%self.param_patt.331]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.956 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.319]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %i32.builtin = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %i32.builtin = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.decl.763c71.2: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.2 = fn_decl @Destroy.WithSelf.SubobjectDestroy.loc6_12.2 [concrete = constants.%Destroy.WithSelf.SubobjectDestroy.d01daf.2] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.6b6 = ref_param_pattern [concrete = constants.%self.param_patt.705]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.6b6 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.70d]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %i32 = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %i32 = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.decl.5f94cf.2: %Destroy.WithSelf.Op.type.ef016f.2 = fn_decl @Destroy.WithSelf.Op.loc6_12.2 [concrete = constants.%Destroy.WithSelf.Op.403171.2] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.6b6 = ref_param_pattern [concrete = constants.%self.param_patt.705]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.6b6 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.70d]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %i32 = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %i32 = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.decl.763c71.3: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.3 = fn_decl @Destroy.WithSelf.SubobjectDestroy.loc6_12.3 [concrete = constants.%Destroy.WithSelf.SubobjectDestroy.d01daf.3] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.c07 = ref_param_pattern [concrete = constants.%self.param_patt.087]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.c07 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.b22]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %array_type = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %array_type = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.decl.5f94cf.3: %Destroy.WithSelf.Op.type.ef016f.3 = fn_decl @Destroy.WithSelf.Op.loc6_12.3 [concrete = constants.%Destroy.WithSelf.Op.403171.3] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.c07 = ref_param_pattern [concrete = constants.%self.param_patt.087]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.c07 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.b22]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %array_type = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %array_type = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @G(%n.param: %i32) -> out %return.param: %i32 {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: %F.ref: %F.type = name_ref F, imports.%Main.F [concrete = constants.%F]
|
||||
@@ -96,26 +163,54 @@ fn F() -> i32 {
|
||||
// CHECK:STDOUT: %specific_fn: <specific function> = specific_function %impl.elem0, @Int.as.Copy.impl.Op(constants.%int_32) [concrete = constants.%Int.as.Copy.impl.Op.specific_fn]
|
||||
// CHECK:STDOUT: %bound_method.loc6_15.2: <bound method> = bound_method %.loc6_15.2, %specific_fn
|
||||
// CHECK:STDOUT: %Int.as.Copy.impl.Op.call: init %i32 = call %bound_method.loc6_15.2(%.loc6_15.2)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.bound: <bound method> = bound_method %.loc6_12.2, constants.%Destroy.WithSelf.Op.403171.3
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call %Destroy.WithSelf.Op.bound(%.loc6_12.2)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.bound: <bound method> = bound_method %.loc6_12.2, constants.%Destroy.WithSelf.SelfDestruct.db3fdb.3
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.call: init %empty_tuple.type = call %Destroy.WithSelf.SelfDestruct.bound(%.loc6_12.2)
|
||||
// CHECK:STDOUT: return %Int.as.Copy.impl.Op.call
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SubobjectDestroy.loc6_12.1(%self.param: ref %i32.builtin) = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc6_12.1(%self.param: ref %i32.builtin) = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc6_12.2(%self.param: ref %i32) {
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SelfDestruct.loc6_12.1(%self.param: ref %i32.builtin) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.Op.decl.5f94cf.1(%self.param)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.SubobjectDestroy.decl.763c71.1(%self.param)
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SubobjectDestroy.loc6_12.2(%self.param: ref %i32) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc6_12.3(%self.param: ref %array_type) {
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc6_12.2(%self.param: ref %i32) = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SelfDestruct.loc6_12.2(%self.param: ref %i32) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.Op.decl.5f94cf.2(%self.param)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.SubobjectDestroy.decl.763c71.2(%self.param)
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SubobjectDestroy.loc6_12.3(%self.param: ref %array_type) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc6_12.3(%self.param: ref %array_type) = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SelfDestruct.loc6_12.3(%self.param: ref %array_type) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.Op.decl.5f94cf.3(%self.param)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.SubobjectDestroy.decl.763c71.3(%self.param)
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: --- import_symbolic_decl.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: %int_1.5b8: Core.IntLiteral = int_value 1 [concrete]
|
||||
|
||||
+99
-4
@@ -49,12 +49,15 @@ fn F(a: array({}, 3)) -> {} {
|
||||
// CHECK:STDOUT: --- function_param.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: %empty_tuple.type: type = tuple_type () [concrete]
|
||||
// CHECK:STDOUT: %N: Core.IntLiteral = symbolic_binding N, 0 [symbolic]
|
||||
// CHECK:STDOUT: %i32: type = class_type @Int, @Int(%int_32) [concrete]
|
||||
// CHECK:STDOUT: %int_3.1ba: Core.IntLiteral = int_value 3 [concrete]
|
||||
// CHECK:STDOUT: %array_type: type = array_type %int_3.1ba, %i32 [concrete]
|
||||
// CHECK:STDOUT: %pattern_type.771: type = pattern_type %array_type [concrete]
|
||||
// CHECK:STDOUT: %pattern_type.6b6: type = pattern_type %i32 [concrete]
|
||||
// CHECK:STDOUT: %F.type: type = fn_type @F [concrete]
|
||||
// CHECK:STDOUT: %F: %F.type = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %i32.builtin: type = int_type signed, %int_32 [concrete]
|
||||
@@ -95,9 +98,28 @@ fn F(a: array({}, 3)) -> {} {
|
||||
// CHECK:STDOUT: %int_3.410: %i32 = int_value 3 [concrete]
|
||||
// CHECK:STDOUT: %array: %array_type = tuple_value (%int_1.0c6, %int_2.295, %int_3.410) [concrete]
|
||||
// CHECK:STDOUT: %.981: ref %array_type = temporary invalid, %array [concrete]
|
||||
// CHECK:STDOUT: %pattern_type.956: type = pattern_type %i32.builtin [concrete]
|
||||
// CHECK:STDOUT: %self.param_patt.331: %pattern_type.956 = ref_param_pattern [concrete]
|
||||
// CHECK:STDOUT: %self.patt.319: %pattern_type.956 = wrapper_binding_pattern self, %self.param_patt.331 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.1: type = fn_type @Destroy.WithSelf.SubobjectDestroy.loc10_20.1 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.d01daf.1: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.1 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.type.ef016f.1: type = fn_type @Destroy.WithSelf.Op.loc10_20.1 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.403171.1: %Destroy.WithSelf.Op.type.ef016f.1 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %self.param_patt.705: %pattern_type.6b6 = ref_param_pattern [concrete]
|
||||
// CHECK:STDOUT: %self.patt.70d: %pattern_type.6b6 = wrapper_binding_pattern self, %self.param_patt.705 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.2: type = fn_type @Destroy.WithSelf.SubobjectDestroy.loc10_20.2 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.d01daf.2: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.2 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.type.ef016f.2: type = fn_type @Destroy.WithSelf.Op.loc10_20.2 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.403171.2: %Destroy.WithSelf.Op.type.ef016f.2 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %self.param_patt.c42: %pattern_type.771 = ref_param_pattern [concrete]
|
||||
// CHECK:STDOUT: %self.patt.3bc: %pattern_type.771 = wrapper_binding_pattern self, %self.param_patt.c42 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.3: type = fn_type @Destroy.WithSelf.SubobjectDestroy.loc10_20.3 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.d01daf.3: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.3 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.type.ef016f.3: type = fn_type @Destroy.WithSelf.Op.loc10_20.3 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.403171.3: %Destroy.WithSelf.Op.type.ef016f.3 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.bound: <bound method> = bound_method %.981, %Destroy.WithSelf.Op.403171.3 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.type.fbceb5.3: type = fn_type @Destroy.WithSelf.SelfDestruct.loc10_20.3 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.db3fdb.3: %Destroy.WithSelf.SelfDestruct.type.fbceb5.3 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.bound: <bound method> = bound_method %.981, %Destroy.WithSelf.SelfDestruct.db3fdb.3 [concrete]
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: imports {
|
||||
@@ -107,6 +129,51 @@ fn F(a: array({}, 3)) -> {} {
|
||||
// CHECK:STDOUT: %ImplicitAs.impl_witness_table.1aa = impl_witness_table (%Core.import_ref.edf), @Core.IntLiteral.as.ImplicitAs.impl [concrete]
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: generated {
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.decl.763c71.1: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.1 = fn_decl @Destroy.WithSelf.SubobjectDestroy.loc10_20.1 [concrete = constants.%Destroy.WithSelf.SubobjectDestroy.d01daf.1] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.956 = ref_param_pattern [concrete = constants.%self.param_patt.331]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.956 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.319]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %i32.builtin = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %i32.builtin = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.decl.5f94cf.1: %Destroy.WithSelf.Op.type.ef016f.1 = fn_decl @Destroy.WithSelf.Op.loc10_20.1 [concrete = constants.%Destroy.WithSelf.Op.403171.1] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.956 = ref_param_pattern [concrete = constants.%self.param_patt.331]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.956 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.319]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %i32.builtin = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %i32.builtin = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.decl.763c71.2: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.2 = fn_decl @Destroy.WithSelf.SubobjectDestroy.loc10_20.2 [concrete = constants.%Destroy.WithSelf.SubobjectDestroy.d01daf.2] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.6b6 = ref_param_pattern [concrete = constants.%self.param_patt.705]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.6b6 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.70d]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %i32 = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %i32 = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.decl.5f94cf.2: %Destroy.WithSelf.Op.type.ef016f.2 = fn_decl @Destroy.WithSelf.Op.loc10_20.2 [concrete = constants.%Destroy.WithSelf.Op.403171.2] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.6b6 = ref_param_pattern [concrete = constants.%self.param_patt.705]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.6b6 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.70d]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %i32 = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %i32 = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.decl.763c71.3: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.3 = fn_decl @Destroy.WithSelf.SubobjectDestroy.loc10_20.3 [concrete = constants.%Destroy.WithSelf.SubobjectDestroy.d01daf.3] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.771 = ref_param_pattern [concrete = constants.%self.param_patt.c42]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.771 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.3bc]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %array_type = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %array_type = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.decl.5f94cf.3: %Destroy.WithSelf.Op.type.ef016f.3 = fn_decl @Destroy.WithSelf.Op.loc10_20.3 [concrete = constants.%Destroy.WithSelf.Op.403171.3] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.771 = ref_param_pattern [concrete = constants.%self.param_patt.c42]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.771 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.3bc]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %array_type = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %array_type = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @F(%arr.param: %array_type, %i.param: %i32) -> out %return.param: %i32 {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: %arr.ref: %array_type = name_ref arr, %arr
|
||||
@@ -170,25 +237,53 @@ fn F(a: array({}, 3)) -> {} {
|
||||
// CHECK:STDOUT: %.loc10_23.1: %i32 = value_of_initializer %Core.IntLiteral.as.ImplicitAs.impl.Convert.call.loc10_23 [concrete = constants.%int_1.0c6]
|
||||
// CHECK:STDOUT: %.loc10_23.2: %i32 = converted %int_1.loc10_23, %.loc10_23.1 [concrete = constants.%int_1.0c6]
|
||||
// CHECK:STDOUT: %F.call: init %i32 = call %F.ref(%.loc10_20.15, %.loc10_23.2)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call constants.%Destroy.WithSelf.Op.bound(constants.%.981)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.call: init %empty_tuple.type = call constants.%Destroy.WithSelf.SelfDestruct.bound(constants.%.981)
|
||||
// CHECK:STDOUT: return %F.call
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SubobjectDestroy.loc10_20.1(%self.param: ref %i32.builtin) = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc10_20.1(%self.param: ref %i32.builtin) = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc10_20.2(%self.param: ref %i32) {
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SelfDestruct.loc10_20.1(%self.param: ref %i32.builtin) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.Op.decl.5f94cf.1(%self.param)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.SubobjectDestroy.decl.763c71.1(%self.param)
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SubobjectDestroy.loc10_20.2(%self.param: ref %i32) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc10_20.3(%self.param: ref %array_type) {
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc10_20.2(%self.param: ref %i32) = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SelfDestruct.loc10_20.2(%self.param: ref %i32) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.Op.decl.5f94cf.2(%self.param)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.SubobjectDestroy.decl.763c71.2(%self.param)
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SubobjectDestroy.loc10_20.3(%self.param: ref %array_type) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc10_20.3(%self.param: ref %array_type) = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SelfDestruct.loc10_20.3(%self.param: ref %array_type) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.Op.decl.5f94cf.3(%self.param)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.SubobjectDestroy.decl.763c71.3(%self.param)
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: --- index_non_literal.carbon
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: constants {
|
||||
// CHECK:STDOUT: type: type = facet_type <type> [concrete]
|
||||
// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [concrete]
|
||||
// CHECK:STDOUT: %empty_struct: %empty_struct_type = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %int_3: Core.IntLiteral = int_value 3 [concrete]
|
||||
|
||||
+185
-60
@@ -59,25 +59,26 @@ fn H() { G(3); }
|
||||
// CHECK:STDOUT: --- generic_empty.carbon
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: constants {
|
||||
// CHECK:STDOUT: %pattern_type.98f: type = pattern_type type [concrete]
|
||||
// CHECK:STDOUT: %T.patt: %pattern_type.98f = symbolic_binding_pattern T, 0 [symbolic]
|
||||
// CHECK:STDOUT: type: type = facet_type <type> [concrete]
|
||||
// CHECK:STDOUT: %pattern_type.9a5: type = pattern_type type [concrete]
|
||||
// CHECK:STDOUT: %T.patt: %pattern_type.9a5 = symbolic_binding_pattern T, 0 [symbolic]
|
||||
// CHECK:STDOUT: %T: type = symbolic_binding T, 0 [symbolic]
|
||||
// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete]
|
||||
// CHECK:STDOUT: %int_0: Core.IntLiteral = int_value 0 [concrete]
|
||||
// CHECK:STDOUT: %array_type.1b3: type = array_type %int_0, %T [symbolic]
|
||||
// CHECK:STDOUT: %require_complete: <witness> = require_complete_type %array_type.1b3 [symbolic]
|
||||
// CHECK:STDOUT: %require_complete.cc5: <witness> = require_complete_type %array_type.1b3 [symbolic]
|
||||
// CHECK:STDOUT: %pattern_type.bd6: type = pattern_type %array_type.1b3 [symbolic]
|
||||
// CHECK:STDOUT: %arr.patt.770: %pattern_type.bd6 = ref_binding_pattern arr [symbolic]
|
||||
// CHECK:STDOUT: %arr.var_patt.5fc: %pattern_type.bd6 = var_pattern %arr.patt.770 [symbolic]
|
||||
// CHECK:STDOUT: %empty_tuple: %empty_tuple.type = tuple_value () [concrete]
|
||||
// CHECK:STDOUT: %array.ca4: %array_type.1b3 = tuple_value () [symbolic]
|
||||
// CHECK:STDOUT: %Destroy.type: type = facet_type <@Destroy> [concrete]
|
||||
// CHECK:STDOUT: %Destroy.lookup_impl_witness: <witness> = lookup_impl_witness %array_type.1b3, @Destroy [symbolic]
|
||||
// CHECK:STDOUT: %Destroy.facet.a52: %Destroy.type = facet_value %array_type.1b3, (%Destroy.lookup_impl_witness) [symbolic]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.type.a67: type = fn_type @Destroy.WithSelf.Op.1, @Destroy.WithSelf(%Destroy.facet.a52) [symbolic]
|
||||
// CHECK:STDOUT: %.1e5: type = fn_type_with_self_type %Destroy.WithSelf.Op.type.a67, %Destroy.facet.a52 [symbolic]
|
||||
// CHECK:STDOUT: %impl.elem0: %.1e5 = impl_witness_access %Destroy.lookup_impl_witness, element0 [symbolic]
|
||||
// CHECK:STDOUT: %specific_impl_fn: <specific function> = specific_impl_function %impl.elem0, @Destroy.WithSelf.Op.1(%Destroy.facet.a52) [symbolic]
|
||||
// CHECK:STDOUT: %Destroy.lookup_impl_witness.ac5: <witness> = lookup_impl_witness %array_type.1b3, @Destroy [symbolic]
|
||||
// CHECK:STDOUT: %Destroy.facet.a52: %Destroy.type = facet_value %array_type.1b3, (%Destroy.lookup_impl_witness.ac5) [symbolic]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.type.e30: type = fn_type @Destroy.WithSelf.SelfDestruct.1, @Destroy.WithSelf(%Destroy.facet.a52) [symbolic]
|
||||
// CHECK:STDOUT: %.c54: type = fn_type_with_self_type %Destroy.WithSelf.SelfDestruct.type.e30, %Destroy.facet.a52 [symbolic]
|
||||
// CHECK:STDOUT: %impl.elem2: %.c54 = impl_witness_access %Destroy.lookup_impl_witness.ac5, element2 [symbolic]
|
||||
// CHECK:STDOUT: %specific_impl_fn.0cb: <specific function> = specific_impl_function %impl.elem2, @Destroy.WithSelf.SelfDestruct.1(%Destroy.facet.a52) [symbolic]
|
||||
// CHECK:STDOUT: %C: type = class_type @C [concrete]
|
||||
// CHECK:STDOUT: %array_type.988: type = array_type %int_0, %C [concrete]
|
||||
// CHECK:STDOUT: %complete_type.e80: <witness> = complete_type_witness %array_type.988 [concrete]
|
||||
@@ -85,12 +86,35 @@ fn H() { G(3); }
|
||||
// CHECK:STDOUT: %arr.patt.c91: %pattern_type.6be = ref_binding_pattern arr [concrete]
|
||||
// CHECK:STDOUT: %arr.var_patt.9dd: %pattern_type.6be = var_pattern %arr.patt.c91 [concrete]
|
||||
// CHECK:STDOUT: %array.497: %array_type.988 = tuple_value () [concrete]
|
||||
// CHECK:STDOUT: %self.param_patt.5c1: %pattern_type.6be = ref_param_pattern [concrete]
|
||||
// CHECK:STDOUT: %self.patt.1a6: %pattern_type.6be = wrapper_binding_pattern self, %self.param_patt.5c1 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.type.dfc: type = fn_type @Destroy.WithSelf.SubobjectDestroy.loc7 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.d01: %Destroy.WithSelf.SubobjectDestroy.type.dfc = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.type.ef0: type = fn_type @Destroy.WithSelf.Op.loc7 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.403: %Destroy.WithSelf.Op.type.ef0 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %custom_witness.6c4: <witness> = custom_witness (%Destroy.WithSelf.Op.403), @Destroy [concrete]
|
||||
// CHECK:STDOUT: %Destroy.facet.a8f: %Destroy.type = facet_value %array_type.988, (%custom_witness.6c4) [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.type.3eb: type = fn_type @Destroy.WithSelf.Op.1, @Destroy.WithSelf(%Destroy.facet.a8f) [concrete]
|
||||
// CHECK:STDOUT: %.2c2: type = fn_type_with_self_type %Destroy.WithSelf.Op.type.3eb, %Destroy.facet.a8f [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.type.fbc: type = fn_type @Destroy.WithSelf.SelfDestruct.loc7 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.db3: %Destroy.WithSelf.SelfDestruct.type.fbc = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %custom_witness.f8f: <witness> = custom_witness (%Destroy.WithSelf.Op.403, %Destroy.WithSelf.SubobjectDestroy.d01, %Destroy.WithSelf.SelfDestruct.db3), @Destroy [concrete]
|
||||
// CHECK:STDOUT: %Destroy.facet.23c: %Destroy.type = facet_value %array_type.988, (%custom_witness.f8f) [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.type.180: type = fn_type @Destroy.WithSelf.SelfDestruct.1, @Destroy.WithSelf(%Destroy.facet.23c) [concrete]
|
||||
// CHECK:STDOUT: %.a26: type = fn_type_with_self_type %Destroy.WithSelf.SelfDestruct.type.180, %Destroy.facet.23c [concrete]
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: generated {
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.decl: %Destroy.WithSelf.SubobjectDestroy.type.dfc = fn_decl @Destroy.WithSelf.SubobjectDestroy.loc7 [concrete = constants.%Destroy.WithSelf.SubobjectDestroy.d01] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.6be = ref_param_pattern [concrete = constants.%self.param_patt.5c1]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.6be = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.1a6]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %array_type.988 = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %array_type.988 = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.decl: %Destroy.WithSelf.Op.type.ef0 = fn_decl @Destroy.WithSelf.Op.loc7 [concrete = constants.%Destroy.WithSelf.Op.403] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.6be = ref_param_pattern [concrete = constants.%self.param_patt.5c1]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.6be = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.1a6]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %array_type.988 = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %array_type.988 = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: generic fn @G(%T.loc4_15.2: type) {
|
||||
@@ -98,17 +122,17 @@ fn H() { G(3); }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: !definition:
|
||||
// CHECK:STDOUT: %array_type.loc7_29.2: type = array_type constants.%int_0, %T.loc4_15.1 [symbolic = %array_type.loc7_29.2 (constants.%array_type.1b3)]
|
||||
// CHECK:STDOUT: %require_complete: <witness> = require_complete_type %array_type.loc7_29.2 [symbolic = %require_complete (constants.%require_complete)]
|
||||
// CHECK:STDOUT: %require_complete: <witness> = require_complete_type %array_type.loc7_29.2 [symbolic = %require_complete (constants.%require_complete.cc5)]
|
||||
// CHECK:STDOUT: %pattern_type: type = pattern_type %array_type.loc7_29.2 [symbolic = %pattern_type (constants.%pattern_type.bd6)]
|
||||
// CHECK:STDOUT: %arr.patt.loc7_17.2: @G.%pattern_type (%pattern_type.bd6) = ref_binding_pattern arr [symbolic = %arr.patt.loc7_17.2 (constants.%arr.patt.770)]
|
||||
// CHECK:STDOUT: %arr.var_patt.loc7_3.2: @G.%pattern_type (%pattern_type.bd6) = var_pattern %arr.patt.loc7_17.2 [symbolic = %arr.var_patt.loc7_3.2 (constants.%arr.var_patt.5fc)]
|
||||
// CHECK:STDOUT: %array: @G.%array_type.loc7_29.2 (%array_type.1b3) = tuple_value () [symbolic = %array (constants.%array.ca4)]
|
||||
// CHECK:STDOUT: %Destroy.lookup_impl_witness: <witness> = lookup_impl_witness %array_type.loc7_29.2, @Destroy [symbolic = %Destroy.lookup_impl_witness (constants.%Destroy.lookup_impl_witness)]
|
||||
// CHECK:STDOUT: %Destroy.lookup_impl_witness: <witness> = lookup_impl_witness %array_type.loc7_29.2, @Destroy [symbolic = %Destroy.lookup_impl_witness (constants.%Destroy.lookup_impl_witness.ac5)]
|
||||
// CHECK:STDOUT: %Destroy.facet.loc7_3.3: %Destroy.type = facet_value %array_type.loc7_29.2, (%Destroy.lookup_impl_witness) [symbolic = %Destroy.facet.loc7_3.3 (constants.%Destroy.facet.a52)]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.type: type = fn_type @Destroy.WithSelf.Op.1, @Destroy.WithSelf(%Destroy.facet.loc7_3.3) [symbolic = %Destroy.WithSelf.Op.type (constants.%Destroy.WithSelf.Op.type.a67)]
|
||||
// CHECK:STDOUT: %.loc7_3.4: type = fn_type_with_self_type %Destroy.WithSelf.Op.type, %Destroy.facet.loc7_3.3 [symbolic = %.loc7_3.4 (constants.%.1e5)]
|
||||
// CHECK:STDOUT: %impl.elem0.loc7_3.2: @G.%.loc7_3.4 (%.1e5) = impl_witness_access %Destroy.lookup_impl_witness, element0 [symbolic = %impl.elem0.loc7_3.2 (constants.%impl.elem0)]
|
||||
// CHECK:STDOUT: %specific_impl_fn.loc7_3.2: <specific function> = specific_impl_function %impl.elem0.loc7_3.2, @Destroy.WithSelf.Op.1(%Destroy.facet.loc7_3.3) [symbolic = %specific_impl_fn.loc7_3.2 (constants.%specific_impl_fn)]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.type: type = fn_type @Destroy.WithSelf.SelfDestruct.1, @Destroy.WithSelf(%Destroy.facet.loc7_3.3) [symbolic = %Destroy.WithSelf.SelfDestruct.type (constants.%Destroy.WithSelf.SelfDestruct.type.e30)]
|
||||
// CHECK:STDOUT: %.loc7_3.4: type = fn_type_with_self_type %Destroy.WithSelf.SelfDestruct.type, %Destroy.facet.loc7_3.3 [symbolic = %.loc7_3.4 (constants.%.c54)]
|
||||
// CHECK:STDOUT: %impl.elem2.loc7_3.2: @G.%.loc7_3.4 (%.c54) = impl_witness_access %Destroy.lookup_impl_witness, element2 [symbolic = %impl.elem2.loc7_3.2 (constants.%impl.elem2)]
|
||||
// CHECK:STDOUT: %specific_impl_fn.loc7_3.2: <specific function> = specific_impl_function %impl.elem2.loc7_3.2, @Destroy.WithSelf.SelfDestruct.1(%Destroy.facet.loc7_3.3) [symbolic = %specific_impl_fn.loc7_3.2 (constants.%specific_impl_fn.0cb)]
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn() {
|
||||
// CHECK:STDOUT: !entry:
|
||||
@@ -127,21 +151,30 @@ fn H() { G(3); }
|
||||
// CHECK:STDOUT: %arr.patt.loc7_17.1: @G.%pattern_type (%pattern_type.bd6) = ref_binding_pattern arr [symbolic = %arr.patt.loc7_17.2 (constants.%arr.patt.770)]
|
||||
// CHECK:STDOUT: %arr.var_patt.loc7_3.1: @G.%pattern_type (%pattern_type.bd6) = var_pattern %arr.patt.loc7_17.1 [symbolic = %arr.var_patt.loc7_3.2 (constants.%arr.var_patt.5fc)]
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %impl.elem0.loc7_3.1: @G.%.loc7_3.4 (%.1e5) = impl_witness_access constants.%Destroy.lookup_impl_witness, element0 [symbolic = %impl.elem0.loc7_3.2 (constants.%impl.elem0)]
|
||||
// CHECK:STDOUT: %bound_method.loc7_3.1: <bound method> = bound_method %arr.var, %impl.elem0.loc7_3.1
|
||||
// CHECK:STDOUT: %Destroy.facet.loc7_3.1: %Destroy.type = facet_value constants.%array_type.1b3, (constants.%Destroy.lookup_impl_witness) [symbolic = %Destroy.facet.loc7_3.3 (constants.%Destroy.facet.a52)]
|
||||
// CHECK:STDOUT: %impl.elem2.loc7_3.1: @G.%.loc7_3.4 (%.c54) = impl_witness_access constants.%Destroy.lookup_impl_witness.ac5, element2 [symbolic = %impl.elem2.loc7_3.2 (constants.%impl.elem2)]
|
||||
// CHECK:STDOUT: %bound_method.loc7_3.1: <bound method> = bound_method %arr.var, %impl.elem2.loc7_3.1
|
||||
// CHECK:STDOUT: %Destroy.facet.loc7_3.1: %Destroy.type = facet_value constants.%array_type.1b3, (constants.%Destroy.lookup_impl_witness.ac5) [symbolic = %Destroy.facet.loc7_3.3 (constants.%Destroy.facet.a52)]
|
||||
// CHECK:STDOUT: %.loc7_3.2: %Destroy.type = converted constants.%array_type.1b3, %Destroy.facet.loc7_3.1 [symbolic = %Destroy.facet.loc7_3.3 (constants.%Destroy.facet.a52)]
|
||||
// CHECK:STDOUT: %Destroy.facet.loc7_3.2: %Destroy.type = facet_value constants.%array_type.1b3, (constants.%Destroy.lookup_impl_witness) [symbolic = %Destroy.facet.loc7_3.3 (constants.%Destroy.facet.a52)]
|
||||
// CHECK:STDOUT: %Destroy.facet.loc7_3.2: %Destroy.type = facet_value constants.%array_type.1b3, (constants.%Destroy.lookup_impl_witness.ac5) [symbolic = %Destroy.facet.loc7_3.3 (constants.%Destroy.facet.a52)]
|
||||
// CHECK:STDOUT: %.loc7_3.3: %Destroy.type = converted constants.%array_type.1b3, %Destroy.facet.loc7_3.2 [symbolic = %Destroy.facet.loc7_3.3 (constants.%Destroy.facet.a52)]
|
||||
// CHECK:STDOUT: %specific_impl_fn.loc7_3.1: <specific function> = specific_impl_function %impl.elem0.loc7_3.1, @Destroy.WithSelf.Op.1(constants.%Destroy.facet.a52) [symbolic = %specific_impl_fn.loc7_3.2 (constants.%specific_impl_fn)]
|
||||
// CHECK:STDOUT: %specific_impl_fn.loc7_3.1: <specific function> = specific_impl_function %impl.elem2.loc7_3.1, @Destroy.WithSelf.SelfDestruct.1(constants.%Destroy.facet.a52) [symbolic = %specific_impl_fn.loc7_3.2 (constants.%specific_impl_fn.0cb)]
|
||||
// CHECK:STDOUT: %bound_method.loc7_3.2: <bound method> = bound_method %arr.var, %specific_impl_fn.loc7_3.1
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call %bound_method.loc7_3.2(%arr.var)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.call: init %empty_tuple.type = call %bound_method.loc7_3.2(%arr.var)
|
||||
// CHECK:STDOUT: <elided>
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SubobjectDestroy.loc7(%self.param: ref %array_type.988) = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc7(%self.param: ref %array_type.988) = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SelfDestruct.loc7(%self.param: ref %array_type.988) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.Op.decl(%self.param)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.SubobjectDestroy.decl(%self.param)
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: specific @G(constants.%T) {
|
||||
// CHECK:STDOUT: %T.patt.loc4_15.2 => constants.%T.patt
|
||||
// CHECK:STDOUT: %T.loc4_15.1 => constants.%T
|
||||
@@ -158,17 +191,18 @@ fn H() { G(3); }
|
||||
// CHECK:STDOUT: %arr.patt.loc7_17.2 => constants.%arr.patt.c91
|
||||
// CHECK:STDOUT: %arr.var_patt.loc7_3.2 => constants.%arr.var_patt.9dd
|
||||
// CHECK:STDOUT: %array => constants.%array.497
|
||||
// CHECK:STDOUT: %Destroy.lookup_impl_witness => constants.%custom_witness.6c4
|
||||
// CHECK:STDOUT: %Destroy.facet.loc7_3.3 => constants.%Destroy.facet.a8f
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.type => constants.%Destroy.WithSelf.Op.type.3eb
|
||||
// CHECK:STDOUT: %.loc7_3.4 => constants.%.2c2
|
||||
// CHECK:STDOUT: %impl.elem0.loc7_3.2 => constants.%Destroy.WithSelf.Op.403
|
||||
// CHECK:STDOUT: %specific_impl_fn.loc7_3.2 => constants.%Destroy.WithSelf.Op.403
|
||||
// CHECK:STDOUT: %Destroy.lookup_impl_witness => constants.%custom_witness.f8f
|
||||
// CHECK:STDOUT: %Destroy.facet.loc7_3.3 => constants.%Destroy.facet.23c
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.type => constants.%Destroy.WithSelf.SelfDestruct.type.180
|
||||
// CHECK:STDOUT: %.loc7_3.4 => constants.%.a26
|
||||
// CHECK:STDOUT: %impl.elem2.loc7_3.2 => constants.%Destroy.WithSelf.SelfDestruct.db3
|
||||
// CHECK:STDOUT: %specific_impl_fn.loc7_3.2 => constants.%Destroy.WithSelf.SelfDestruct.db3
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: --- fail_todo_init_template_dependent_bound.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: %empty_tuple.type: type = tuple_type () [concrete]
|
||||
// CHECK:STDOUT: %i32: type = class_type @Int, @Int(%int_32) [concrete]
|
||||
@@ -189,14 +223,14 @@ fn H() { G(3); }
|
||||
// CHECK:STDOUT: %tuple: %tuple.type = tuple_value (%int_1, %int_2, %int_3.1ba) [concrete]
|
||||
// CHECK:STDOUT: %Destroy.type: type = facet_type <@Destroy> [concrete]
|
||||
// CHECK:STDOUT: %Self.0e7: %Destroy.type = symbolic_binding Self, 0 [symbolic]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.type.d3e: type = fn_type @Destroy.WithSelf.Op.1, @Destroy.WithSelf(%Self.0e7) [symbolic]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.42b: %Destroy.WithSelf.Op.type.d3e = struct_value () [symbolic]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.type.4e5: type = fn_type @Destroy.WithSelf.SelfDestruct.1, @Destroy.WithSelf(%Self.0e7) [symbolic]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.4a0: %Destroy.WithSelf.SelfDestruct.type.4e5 = struct_value () [symbolic]
|
||||
// CHECK:STDOUT: %Destroy.assoc_type: type = assoc_entity_type @Destroy [concrete]
|
||||
// CHECK:STDOUT: %assoc0.ae8: %Destroy.assoc_type = assoc_entity element0, imports.%Core.import_ref.918 [concrete]
|
||||
// CHECK:STDOUT: %.0be: type = type_of_inst @G.%.loc11_3.5 [template]
|
||||
// CHECK:STDOUT: %.db2: %.0be = splice_inst @G.%.loc11_3.5 [template]
|
||||
// CHECK:STDOUT: %.76e: type = type_of_inst @G.%.loc11_3.8 [template]
|
||||
// CHECK:STDOUT: %.772: %.76e = splice_inst @G.%.loc11_3.8 [template]
|
||||
// CHECK:STDOUT: %assoc2: %Destroy.assoc_type = assoc_entity element2, imports.%Core.import_ref.71d [concrete]
|
||||
// CHECK:STDOUT: %.19c: type = type_of_inst @G.%.loc11_3.5 [template]
|
||||
// CHECK:STDOUT: %.11f: %.19c = splice_inst @G.%.loc11_3.5 [template]
|
||||
// CHECK:STDOUT: %.e86: type = type_of_inst @G.%.loc11_3.8 [template]
|
||||
// CHECK:STDOUT: %.9d4: %.e86 = splice_inst @G.%.loc11_3.8 [template]
|
||||
// CHECK:STDOUT: %From: Core.IntLiteral = symbolic_binding From, 0 [symbolic]
|
||||
// CHECK:STDOUT: %ImplicitAs.type.7cb: type = facet_type <@ImplicitAs, @ImplicitAs(Core.IntLiteral)> [concrete]
|
||||
// CHECK:STDOUT: %Int.as.ImplicitAs.impl.Convert.type.48d: type = fn_type @Int.as.ImplicitAs.impl.Convert, @Int.as.ImplicitAs.impl(%From) [symbolic]
|
||||
@@ -226,30 +260,94 @@ fn H() { G(3); }
|
||||
// CHECK:STDOUT: %pattern_type.771: type = pattern_type %array_type.dc7 [concrete]
|
||||
// CHECK:STDOUT: %arr.patt.316: %pattern_type.771 = ref_binding_pattern arr [concrete]
|
||||
// CHECK:STDOUT: %arr.var_patt.c63: %pattern_type.771 = var_pattern %arr.patt.316 [concrete]
|
||||
// CHECK:STDOUT: %pattern_type.956: type = pattern_type %i32.builtin [concrete]
|
||||
// CHECK:STDOUT: %self.param_patt.331: %pattern_type.956 = ref_param_pattern [concrete]
|
||||
// CHECK:STDOUT: %self.patt.319: %pattern_type.956 = wrapper_binding_pattern self, %self.param_patt.331 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.1: type = fn_type @Destroy.WithSelf.SubobjectDestroy.loc11_3.1 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.d01daf.1: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.1 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.type.ef016f.1: type = fn_type @Destroy.WithSelf.Op.loc11_3.1 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.403171.1: %Destroy.WithSelf.Op.type.ef016f.1 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %self.param_patt.705: %pattern_type.6b6 = ref_param_pattern [concrete]
|
||||
// CHECK:STDOUT: %self.patt.70d: %pattern_type.6b6 = wrapper_binding_pattern self, %self.param_patt.705 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.2: type = fn_type @Destroy.WithSelf.SubobjectDestroy.loc11_3.2 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.d01daf.2: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.2 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.type.ef016f.2: type = fn_type @Destroy.WithSelf.Op.loc11_3.2 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.403171.2: %Destroy.WithSelf.Op.type.ef016f.2 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %self.param_patt.c42: %pattern_type.771 = ref_param_pattern [concrete]
|
||||
// CHECK:STDOUT: %self.patt.3bc: %pattern_type.771 = wrapper_binding_pattern self, %self.param_patt.c42 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.3: type = fn_type @Destroy.WithSelf.SubobjectDestroy.loc11_3.3 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.d01daf.3: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.3 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.type.ef016f.3: type = fn_type @Destroy.WithSelf.Op.loc11_3.3 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.403171.3: %Destroy.WithSelf.Op.type.ef016f.3 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %custom_witness.6c4ec3.3: <witness> = custom_witness (%Destroy.WithSelf.Op.403171.3), @Destroy [concrete]
|
||||
// CHECK:STDOUT: %Destroy.facet.87b: %Destroy.type = facet_value %array_type.dc7, (%custom_witness.6c4ec3.3) [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.type.d31: type = fn_type @Destroy.WithSelf.Op.1, @Destroy.WithSelf(%Destroy.facet.87b) [concrete]
|
||||
// CHECK:STDOUT: %.6e0: type = fn_type_with_self_type %Destroy.WithSelf.Op.type.d31, %Destroy.facet.87b [concrete]
|
||||
// CHECK:STDOUT: %inst.splice_block.c0d: <instruction> = inst_value [concrete] {
|
||||
// CHECK:STDOUT: %.729: <bound method> = splice_block %bound_method.7de {
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.type.fbceb5.3: type = fn_type @Destroy.WithSelf.SelfDestruct.loc11_3.3 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.db3fdb.3: %Destroy.WithSelf.SelfDestruct.type.fbceb5.3 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %custom_witness.f8f19d.3: <witness> = custom_witness (%Destroy.WithSelf.Op.403171.3, %Destroy.WithSelf.SubobjectDestroy.d01daf.3, %Destroy.WithSelf.SelfDestruct.db3fdb.3), @Destroy [concrete]
|
||||
// CHECK:STDOUT: %Destroy.facet.149: %Destroy.type = facet_value %array_type.dc7, (%custom_witness.f8f19d.3) [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.type.95e: type = fn_type @Destroy.WithSelf.SelfDestruct.1, @Destroy.WithSelf(%Destroy.facet.149) [concrete]
|
||||
// CHECK:STDOUT: %.7f7: type = fn_type_with_self_type %Destroy.WithSelf.SelfDestruct.type.95e, %Destroy.facet.149 [concrete]
|
||||
// CHECK:STDOUT: %inst.splice_block.11c: <instruction> = inst_value [concrete] {
|
||||
// CHECK:STDOUT: %.214: <bound method> = splice_block %bound_method.e27 {
|
||||
// CHECK:STDOUT: %.875: ref %array_type.dc7 = specific_inst @G.%arr.var, @G(%int_3.410)
|
||||
// CHECK:STDOUT: %impl.elem0.6b3: %.6e0 = impl_witness_access %custom_witness.6c4ec3.3, element0 [concrete = %Destroy.WithSelf.Op.403171.3]
|
||||
// CHECK:STDOUT: %bound_method.7de: <bound method> = bound_method %.875, %impl.elem0.6b3
|
||||
// CHECK:STDOUT: %impl.elem2: %.7f7 = impl_witness_access %custom_witness.f8f19d.3, element2 [concrete = %Destroy.WithSelf.SelfDestruct.db3fdb.3]
|
||||
// CHECK:STDOUT: %bound_method.e27: <bound method> = bound_method %.875, %impl.elem2
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %inst.call: <instruction> = inst_value [concrete] {
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call %.729(%.875)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.call: init %empty_tuple.type = call %.214(%.875)
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: imports {
|
||||
// CHECK:STDOUT: %Core.import_ref.918: @Destroy.WithSelf.%Destroy.WithSelf.Op.type (%Destroy.WithSelf.Op.type.d3e) = import_ref Core//prelude/parts/destroy, loc{{\d+_\d+}}, loaded [symbolic = @Destroy.WithSelf.%Destroy.WithSelf.Op (constants.%Destroy.WithSelf.Op.42b)]
|
||||
// CHECK:STDOUT: %Core.import_ref.71d: @Destroy.WithSelf.%Destroy.WithSelf.SelfDestruct.type (%Destroy.WithSelf.SelfDestruct.type.4e5) = import_ref Core//prelude/parts/destroy, loc{{\d+_\d+}}, loaded [symbolic = @Destroy.WithSelf.%Destroy.WithSelf.SelfDestruct (constants.%Destroy.WithSelf.SelfDestruct.4a0)]
|
||||
// CHECK:STDOUT: %Core.import_ref.32e: @Int.as.ImplicitAs.impl.%Int.as.ImplicitAs.impl.Convert.type (%Int.as.ImplicitAs.impl.Convert.type.48d) = import_ref Core//prelude/parts/int, loc{{\d+_\d+}}, loaded [symbolic = @Int.as.ImplicitAs.impl.%Int.as.ImplicitAs.impl.Convert (constants.%Int.as.ImplicitAs.impl.Convert.0f9)]
|
||||
// CHECK:STDOUT: %ImplicitAs.impl_witness_table.204 = impl_witness_table (%Core.import_ref.32e), @Int.as.ImplicitAs.impl [concrete]
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: generated {
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.decl.763c71.1: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.1 = fn_decl @Destroy.WithSelf.SubobjectDestroy.loc11_3.1 [concrete = constants.%Destroy.WithSelf.SubobjectDestroy.d01daf.1] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.956 = ref_param_pattern [concrete = constants.%self.param_patt.331]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.956 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.319]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %i32.builtin = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %i32.builtin = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.decl.5f94cf.1: %Destroy.WithSelf.Op.type.ef016f.1 = fn_decl @Destroy.WithSelf.Op.loc11_3.1 [concrete = constants.%Destroy.WithSelf.Op.403171.1] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.956 = ref_param_pattern [concrete = constants.%self.param_patt.331]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.956 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.319]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %i32.builtin = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %i32.builtin = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.decl.763c71.2: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.2 = fn_decl @Destroy.WithSelf.SubobjectDestroy.loc11_3.2 [concrete = constants.%Destroy.WithSelf.SubobjectDestroy.d01daf.2] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.6b6 = ref_param_pattern [concrete = constants.%self.param_patt.705]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.6b6 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.70d]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %i32 = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %i32 = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.decl.5f94cf.2: %Destroy.WithSelf.Op.type.ef016f.2 = fn_decl @Destroy.WithSelf.Op.loc11_3.2 [concrete = constants.%Destroy.WithSelf.Op.403171.2] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.6b6 = ref_param_pattern [concrete = constants.%self.param_patt.705]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.6b6 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.70d]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %i32 = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %i32 = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.decl.763c71.3: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.3 = fn_decl @Destroy.WithSelf.SubobjectDestroy.loc11_3.3 [concrete = constants.%Destroy.WithSelf.SubobjectDestroy.d01daf.3] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.771 = ref_param_pattern [concrete = constants.%self.param_patt.c42]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.771 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.3bc]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %array_type.dc7 = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %array_type.dc7 = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.decl.5f94cf.3: %Destroy.WithSelf.Op.type.ef016f.3 = fn_decl @Destroy.WithSelf.Op.loc11_3.3 [concrete = constants.%Destroy.WithSelf.Op.403171.3] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.771 = ref_param_pattern [concrete = constants.%self.param_patt.c42]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.771 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.3bc]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %array_type.dc7 = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %array_type.dc7 = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: generic fn @G(%N.loc5_16.2: %i32) {
|
||||
// CHECK:STDOUT: <elided>
|
||||
// CHECK:STDOUT:
|
||||
@@ -261,12 +359,12 @@ fn H() { G(3); }
|
||||
// CHECK:STDOUT: %pattern_type: type = pattern_type %array_type.loc11_31.2 [template = %pattern_type (constants.%pattern_type.ed3)]
|
||||
// CHECK:STDOUT: %arr.patt.loc11_17.2: @G.%pattern_type (%pattern_type.ed3) = ref_binding_pattern arr [template = %arr.patt.loc11_17.2 (constants.%arr.patt.515)]
|
||||
// CHECK:STDOUT: %arr.var_patt.loc11_3.2: @G.%pattern_type (%pattern_type.ed3) = var_pattern %arr.patt.loc11_17.2 [template = %arr.var_patt.loc11_3.2 (constants.%arr.var_patt.821)]
|
||||
// CHECK:STDOUT: %.loc11_3.5: <instruction> = compound_member_access_action %arr.var, constants.%assoc0.ae8 [template]
|
||||
// CHECK:STDOUT: %.loc11_3.6: type = type_of_inst %.loc11_3.5 [template = %.loc11_3.6 (constants.%.0be)]
|
||||
// CHECK:STDOUT: %.loc11_3.7: @G.%.loc11_3.6 (%.0be) = splice_inst %.loc11_3.5 [template = %.loc11_3.7 (constants.%.db2)]
|
||||
// CHECK:STDOUT: %.loc11_3.5: <instruction> = compound_member_access_action %arr.var, constants.%assoc2 [template]
|
||||
// CHECK:STDOUT: %.loc11_3.6: type = type_of_inst %.loc11_3.5 [template = %.loc11_3.6 (constants.%.19c)]
|
||||
// CHECK:STDOUT: %.loc11_3.7: @G.%.loc11_3.6 (%.19c) = splice_inst %.loc11_3.5 [template = %.loc11_3.7 (constants.%.11f)]
|
||||
// CHECK:STDOUT: %.loc11_3.8: <instruction> = call_action (%.loc11_3.2), true [template]
|
||||
// CHECK:STDOUT: %.loc11_3.9: type = type_of_inst %.loc11_3.8 [template = %.loc11_3.9 (constants.%.76e)]
|
||||
// CHECK:STDOUT: %.loc11_3.10: @G.%.loc11_3.9 (%.76e) = splice_inst %.loc11_3.8 [template = %.loc11_3.10 (constants.%.772)]
|
||||
// CHECK:STDOUT: %.loc11_3.9: type = type_of_inst %.loc11_3.8 [template = %.loc11_3.9 (constants.%.e86)]
|
||||
// CHECK:STDOUT: %.loc11_3.10: @G.%.loc11_3.9 (%.e86) = splice_inst %.loc11_3.8 [template = %.loc11_3.10 (constants.%.9d4)]
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn() {
|
||||
// CHECK:STDOUT: !entry:
|
||||
@@ -287,23 +385,50 @@ fn H() { G(3); }
|
||||
// CHECK:STDOUT: %arr.patt.loc11_17.1: @G.%pattern_type (%pattern_type.ed3) = ref_binding_pattern arr [template = %arr.patt.loc11_17.2 (constants.%arr.patt.515)]
|
||||
// CHECK:STDOUT: %arr.var_patt.loc11_3.1: @G.%pattern_type (%pattern_type.ed3) = var_pattern %arr.patt.loc11_17.1 [template = %arr.var_patt.loc11_3.2 (constants.%arr.var_patt.821)]
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %.loc11_3.1: type = type_of_inst %.loc11_3.5 [template = %.loc11_3.6 (constants.%.0be)]
|
||||
// CHECK:STDOUT: %.loc11_3.2: @G.%.loc11_3.6 (%.0be) = splice_inst %.loc11_3.5 [template = %.loc11_3.7 (constants.%.db2)]
|
||||
// CHECK:STDOUT: %.loc11_3.3: type = type_of_inst %.loc11_3.8 [template = %.loc11_3.9 (constants.%.76e)]
|
||||
// CHECK:STDOUT: %.loc11_3.4: @G.%.loc11_3.9 (%.76e) = splice_inst %.loc11_3.8 [template = %.loc11_3.10 (constants.%.772)]
|
||||
// CHECK:STDOUT: %.loc11_3.1: type = type_of_inst %.loc11_3.5 [template = %.loc11_3.6 (constants.%.19c)]
|
||||
// CHECK:STDOUT: %.loc11_3.2: @G.%.loc11_3.6 (%.19c) = splice_inst %.loc11_3.5 [template = %.loc11_3.7 (constants.%.11f)]
|
||||
// CHECK:STDOUT: %.loc11_3.3: type = type_of_inst %.loc11_3.8 [template = %.loc11_3.9 (constants.%.e86)]
|
||||
// CHECK:STDOUT: %.loc11_3.4: @G.%.loc11_3.9 (%.e86) = splice_inst %.loc11_3.8 [template = %.loc11_3.10 (constants.%.9d4)]
|
||||
// CHECK:STDOUT: <elided>
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SubobjectDestroy.loc11_3.1(%self.param: ref %i32.builtin) = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc11_3.1(%self.param: ref %i32.builtin) = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc11_3.2(%self.param: ref %i32) {
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SelfDestruct.loc11_3.1(%self.param: ref %i32.builtin) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.Op.decl.5f94cf.1(%self.param)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.SubobjectDestroy.decl.763c71.1(%self.param)
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SubobjectDestroy.loc11_3.2(%self.param: ref %i32) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc11_3.3(%self.param: ref %array_type.dc7) {
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc11_3.2(%self.param: ref %i32) = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SelfDestruct.loc11_3.2(%self.param: ref %i32) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.Op.decl.5f94cf.2(%self.param)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.SubobjectDestroy.decl.763c71.2(%self.param)
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SubobjectDestroy.loc11_3.3(%self.param: ref %array_type.dc7) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc11_3.3(%self.param: ref %array_type.dc7) = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SelfDestruct.loc11_3.3(%self.param: ref %array_type.dc7) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.Op.decl.5f94cf.3(%self.param)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.SubobjectDestroy.decl.763c71.3(%self.param)
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
@@ -324,7 +449,7 @@ fn H() { G(3); }
|
||||
// CHECK:STDOUT: %pattern_type => constants.%pattern_type.771
|
||||
// CHECK:STDOUT: %arr.patt.loc11_17.2 => constants.%arr.patt.316
|
||||
// CHECK:STDOUT: %arr.var_patt.loc11_3.2 => constants.%arr.var_patt.c63
|
||||
// CHECK:STDOUT: %.loc11_3.5 => constants.%inst.splice_block.c0d
|
||||
// CHECK:STDOUT: %.loc11_3.5 => constants.%inst.splice_block.11c
|
||||
// CHECK:STDOUT: %.loc11_3.6 => <bound method>
|
||||
// CHECK:STDOUT: %.loc11_3.7 => invalid
|
||||
// CHECK:STDOUT: %.loc11_3.8 => constants.%inst.call
|
||||
|
||||
+8
-3
@@ -173,6 +173,7 @@ var b: B = {.x = ()} as B;
|
||||
// CHECK:STDOUT: --- adapt_class.carbon
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: constants {
|
||||
// CHECK:STDOUT: type: type = facet_type <type> [concrete]
|
||||
// CHECK:STDOUT: %A: type = class_type @A [concrete]
|
||||
// CHECK:STDOUT: %A.Make.type: type = fn_type @A.Make [concrete]
|
||||
// CHECK:STDOUT: %A.Make: %A.Make.type = struct_value () [concrete]
|
||||
@@ -237,6 +238,7 @@ var b: B = {.x = ()} as B;
|
||||
// CHECK:STDOUT: --- adapt_i32.carbon
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: constants {
|
||||
// CHECK:STDOUT: type: type = facet_type <type> [concrete]
|
||||
// CHECK:STDOUT: %A: type = class_type @A [concrete]
|
||||
// CHECK:STDOUT: %int_32: Core.IntLiteral = int_value 32 [concrete]
|
||||
// CHECK:STDOUT: %i32: type = class_type @Int, @Int(%int_32) [concrete]
|
||||
@@ -304,6 +306,7 @@ var b: B = {.x = ()} as B;
|
||||
// CHECK:STDOUT: --- multi_level_adapt.carbon
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: constants {
|
||||
// CHECK:STDOUT: type: type = facet_type <type> [concrete]
|
||||
// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [concrete]
|
||||
// CHECK:STDOUT: %empty_struct: %empty_struct_type = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %D: type = class_type @D [concrete]
|
||||
@@ -333,6 +336,7 @@ var b: B = {.x = ()} as B;
|
||||
// CHECK:STDOUT: --- init_class_value.carbon
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: constants {
|
||||
// CHECK:STDOUT: type: type = facet_type <type> [concrete]
|
||||
// CHECK:STDOUT: %A: type = class_type @A [concrete]
|
||||
// CHECK:STDOUT: %int_32: Core.IntLiteral = int_value 32 [concrete]
|
||||
// CHECK:STDOUT: %i32: type = class_type @Int, @Int(%int_32) [concrete]
|
||||
@@ -414,12 +418,13 @@ var b: B = {.x = ()} as B;
|
||||
// CHECK:STDOUT: --- init_tuple_value.carbon
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: constants {
|
||||
// CHECK:STDOUT: type: type = facet_type <type> [concrete]
|
||||
// CHECK:STDOUT: %Noncopyable: type = class_type @Noncopyable [concrete]
|
||||
// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [concrete]
|
||||
// CHECK:STDOUT: %A: type = class_type @A [concrete]
|
||||
// CHECK:STDOUT: %empty_struct: %empty_struct_type = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %tuple.type.c8c: type = tuple_type (%empty_struct_type, type) [concrete]
|
||||
// CHECK:STDOUT: %tuple: %tuple.type.c8c = tuple_value (%empty_struct, %Noncopyable) [concrete]
|
||||
// CHECK:STDOUT: %tuple.type.12a: type = tuple_type (%empty_struct_type, type) [concrete]
|
||||
// CHECK:STDOUT: %tuple: %tuple.type.12a = tuple_value (%empty_struct, %Noncopyable) [concrete]
|
||||
// CHECK:STDOUT: %tuple.type.c68: type = tuple_type (%empty_struct_type, %Noncopyable) [concrete]
|
||||
// CHECK:STDOUT: %pattern_type: type = pattern_type %A [concrete]
|
||||
// CHECK:STDOUT: %a_value.patt: %pattern_type = value_binding_pattern a_value [concrete]
|
||||
@@ -430,7 +435,7 @@ var b: B = {.x = ()} as B;
|
||||
// CHECK:STDOUT: %a.ref: %A = name_ref a, %a
|
||||
// CHECK:STDOUT: %.loc14_35: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct]
|
||||
// CHECK:STDOUT: %Noncopyable.ref: type = name_ref Noncopyable, file.%Noncopyable.decl [concrete = constants.%Noncopyable]
|
||||
// CHECK:STDOUT: %.loc14_49.1: %tuple.type.c8c = tuple_literal (%.loc14_35, %Noncopyable.ref) [concrete = constants.%tuple]
|
||||
// CHECK:STDOUT: %.loc14_49.1: %tuple.type.12a = tuple_literal (%.loc14_35, %Noncopyable.ref) [concrete = constants.%tuple]
|
||||
// CHECK:STDOUT: %.loc14_49.2: type = converted constants.%empty_struct, constants.%empty_struct_type [concrete = constants.%empty_struct_type]
|
||||
// CHECK:STDOUT: %.loc14_49.3: type = converted %.loc14_49.1, constants.%tuple.type.c68 [concrete = constants.%tuple.type.c68]
|
||||
// CHECK:STDOUT: %.loc14_30.1: %tuple.type.c68 = as_compatible %a.ref
|
||||
|
||||
+176
-17
@@ -131,6 +131,7 @@ let n: {.x: ()} = {.x = ()} as {.x = ()};
|
||||
// CHECK:STDOUT: --- simple_as.carbon
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: constants {
|
||||
// CHECK:STDOUT: type: type = facet_type <type> [concrete]
|
||||
// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete]
|
||||
// CHECK:STDOUT: %empty_tuple: %empty_tuple.type = tuple_value () [concrete]
|
||||
// CHECK:STDOUT: %struct_type.x.y: type = struct_type {.x: %empty_tuple.type, .y: %empty_tuple.type} [concrete]
|
||||
@@ -173,6 +174,7 @@ let n: {.x: ()} = {.x = ()} as {.x = ()};
|
||||
// CHECK:STDOUT: --- as_type.carbon
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: constants {
|
||||
// CHECK:STDOUT: type: type = facet_type <type> [concrete]
|
||||
// CHECK:STDOUT: %pattern_type: type = pattern_type type [concrete]
|
||||
// CHECK:STDOUT: %t.patt: %pattern_type = value_binding_pattern t [concrete]
|
||||
// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [concrete]
|
||||
@@ -204,22 +206,88 @@ let n: {.x: ()} = {.x = ()} as {.x = ()};
|
||||
// CHECK:STDOUT: --- as_tuple.carbon
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: constants {
|
||||
// CHECK:STDOUT: type: type = facet_type <type> [concrete]
|
||||
// CHECK:STDOUT: %X: type = class_type @X [concrete]
|
||||
// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [concrete]
|
||||
// CHECK:STDOUT: %pattern_type.6cb: type = pattern_type %X [concrete]
|
||||
// CHECK:STDOUT: %Make.type: type = fn_type @Make [concrete]
|
||||
// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete]
|
||||
// CHECK:STDOUT: %Make: %Make.type = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %tuple.type.24b: type = tuple_type (type, type) [concrete]
|
||||
// CHECK:STDOUT: %tuple: %tuple.type.24b = tuple_value (%X, %X) [concrete]
|
||||
// CHECK:STDOUT: %tuple.type.693: type = tuple_type (type, type) [concrete]
|
||||
// CHECK:STDOUT: %tuple: %tuple.type.693 = tuple_value (%X, %X) [concrete]
|
||||
// CHECK:STDOUT: %tuple.type.359: type = tuple_type (%X, %X) [concrete]
|
||||
// CHECK:STDOUT: %pattern_type.3ee: type = pattern_type %tuple.type.359 [concrete]
|
||||
// CHECK:STDOUT: %a.patt: %pattern_type.3ee = value_binding_pattern a [concrete]
|
||||
// CHECK:STDOUT: %pattern_type.a96: type = pattern_type %empty_struct_type [concrete]
|
||||
// CHECK:STDOUT: %self.param_patt.52f: %pattern_type.a96 = ref_param_pattern [concrete]
|
||||
// CHECK:STDOUT: %self.patt.4b1: %pattern_type.a96 = wrapper_binding_pattern self, %self.param_patt.52f [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.1: type = fn_type @Destroy.WithSelf.SubobjectDestroy.loc13_40.1 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.d01daf.1: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.1 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.type.ef016f.1: type = fn_type @Destroy.WithSelf.Op.loc13_40.1 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.403171.1: %Destroy.WithSelf.Op.type.ef016f.1 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %self.param_patt.925: %pattern_type.6cb = ref_param_pattern [concrete]
|
||||
// CHECK:STDOUT: %self.patt.617: %pattern_type.6cb = wrapper_binding_pattern self, %self.param_patt.925 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.2: type = fn_type @Destroy.WithSelf.SubobjectDestroy.loc13_40.2 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.d01daf.2: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.2 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.type.ef016f.2: type = fn_type @Destroy.WithSelf.Op.loc13_40.2 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.403171.2: %Destroy.WithSelf.Op.type.ef016f.2 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.type.fbceb5.2: type = fn_type @Destroy.WithSelf.SelfDestruct.loc13_40.2 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.db3fdb.2: %Destroy.WithSelf.SelfDestruct.type.fbceb5.2 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %b.patt: %pattern_type.3ee = ref_binding_pattern b [concrete]
|
||||
// CHECK:STDOUT: %b.var_patt: %pattern_type.3ee = var_pattern %b.patt [concrete]
|
||||
// CHECK:STDOUT: %self.param_patt.bc5: %pattern_type.3ee = ref_param_pattern [concrete]
|
||||
// CHECK:STDOUT: %self.patt.15e: %pattern_type.3ee = wrapper_binding_pattern self, %self.param_patt.bc5 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.3: type = fn_type @Destroy.WithSelf.SubobjectDestroy.loc20 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.d01daf.3: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.3 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.type.ef016f.3: type = fn_type @Destroy.WithSelf.Op.loc20 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.403171.3: %Destroy.WithSelf.Op.type.ef016f.3 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.type.fbceb5.3: type = fn_type @Destroy.WithSelf.SelfDestruct.loc20 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.db3fdb.3: %Destroy.WithSelf.SelfDestruct.type.fbceb5.3 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: generated {
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.decl.763c71.1: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.1 = fn_decl @Destroy.WithSelf.SubobjectDestroy.loc13_40.1 [concrete = constants.%Destroy.WithSelf.SubobjectDestroy.d01daf.1] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.a96 = ref_param_pattern [concrete = constants.%self.param_patt.52f]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.a96 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.4b1]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %empty_struct_type = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %empty_struct_type = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.decl.5f94cf.1: %Destroy.WithSelf.Op.type.ef016f.1 = fn_decl @Destroy.WithSelf.Op.loc13_40.1 [concrete = constants.%Destroy.WithSelf.Op.403171.1] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.a96 = ref_param_pattern [concrete = constants.%self.param_patt.52f]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.a96 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.4b1]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %empty_struct_type = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %empty_struct_type = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.decl.763c71.2: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.2 = fn_decl @Destroy.WithSelf.SubobjectDestroy.loc13_40.2 [concrete = constants.%Destroy.WithSelf.SubobjectDestroy.d01daf.2] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.6cb = ref_param_pattern [concrete = constants.%self.param_patt.925]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.6cb = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.617]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %X = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %X = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.decl.5f94cf.2: %Destroy.WithSelf.Op.type.ef016f.2 = fn_decl @Destroy.WithSelf.Op.loc13_40.2 [concrete = constants.%Destroy.WithSelf.Op.403171.2] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.6cb = ref_param_pattern [concrete = constants.%self.param_patt.925]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.6cb = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.617]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %X = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %X = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.decl.763c71.3: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.3 = fn_decl @Destroy.WithSelf.SubobjectDestroy.loc20 [concrete = constants.%Destroy.WithSelf.SubobjectDestroy.d01daf.3] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.3ee = ref_param_pattern [concrete = constants.%self.param_patt.bc5]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.3ee = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.15e]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %tuple.type.359 = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %tuple.type.359 = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.decl.5f94cf.3: %Destroy.WithSelf.Op.type.ef016f.3 = fn_decl @Destroy.WithSelf.Op.loc20 [concrete = constants.%Destroy.WithSelf.Op.403171.3] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.3ee = ref_param_pattern [concrete = constants.%self.param_patt.bc5]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.3ee = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.15e]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %tuple.type.359 = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %tuple.type.359 = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Let() {
|
||||
@@ -233,7 +301,7 @@ let n: {.x: ()} = {.x = ()} as {.x = ()};
|
||||
// CHECK:STDOUT: %.loc13_41.1: %tuple.type.359 = tuple_literal (%Make.call.loc13_32, %Make.call.loc13_40)
|
||||
// CHECK:STDOUT: %X.ref.loc13_47: type = name_ref X, file.%X.decl [concrete = constants.%X]
|
||||
// CHECK:STDOUT: %X.ref.loc13_50: type = name_ref X, file.%X.decl [concrete = constants.%X]
|
||||
// CHECK:STDOUT: %.loc13_51.1: %tuple.type.24b = tuple_literal (%X.ref.loc13_47, %X.ref.loc13_50) [concrete = constants.%tuple]
|
||||
// CHECK:STDOUT: %.loc13_51.1: %tuple.type.693 = tuple_literal (%X.ref.loc13_47, %X.ref.loc13_50) [concrete = constants.%tuple]
|
||||
// CHECK:STDOUT: %.loc13_51.2: type = converted %.loc13_51.1, constants.%tuple.type.359 [concrete = constants.%tuple.type.359]
|
||||
// CHECK:STDOUT: %.loc13_32.2: ref %X = temporary %.loc13_32.1, %Make.call.loc13_32
|
||||
// CHECK:STDOUT: %.loc13_32.3: %X = acquire_value %.loc13_32.2
|
||||
@@ -244,24 +312,42 @@ let n: {.x: ()} = {.x = ()} as {.x = ()};
|
||||
// CHECK:STDOUT: %.loc13_22.1: type = splice_block %.loc13_22.3 [concrete = constants.%tuple.type.359] {
|
||||
// CHECK:STDOUT: %X.ref.loc13_18: type = name_ref X, file.%X.decl [concrete = constants.%X]
|
||||
// CHECK:STDOUT: %X.ref.loc13_21: type = name_ref X, file.%X.decl [concrete = constants.%X]
|
||||
// CHECK:STDOUT: %.loc13_22.2: %tuple.type.24b = tuple_literal (%X.ref.loc13_18, %X.ref.loc13_21) [concrete = constants.%tuple]
|
||||
// CHECK:STDOUT: %.loc13_22.2: %tuple.type.693 = tuple_literal (%X.ref.loc13_18, %X.ref.loc13_21) [concrete = constants.%tuple]
|
||||
// CHECK:STDOUT: %.loc13_22.3: type = converted %.loc13_22.2, constants.%tuple.type.359 [concrete = constants.%tuple.type.359]
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %a: %tuple.type.359 = wrapper_binding a, %.loc13_41.2
|
||||
// CHECK:STDOUT: name_binding_decl {
|
||||
// CHECK:STDOUT: %a.patt: %pattern_type.3ee = value_binding_pattern a [concrete = constants.%a.patt]
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.bound.loc13_40: <bound method> = bound_method %.loc13_40.2, constants.%Destroy.WithSelf.Op.403171.2
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call.loc13_40: init %empty_tuple.type = call %Destroy.WithSelf.Op.bound.loc13_40(%.loc13_40.2)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.bound.loc13_32: <bound method> = bound_method %.loc13_32.2, constants.%Destroy.WithSelf.Op.403171.2
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call.loc13_32: init %empty_tuple.type = call %Destroy.WithSelf.Op.bound.loc13_32(%.loc13_32.2)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.bound.loc13_40: <bound method> = bound_method %.loc13_40.2, constants.%Destroy.WithSelf.SelfDestruct.db3fdb.2
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.call.loc13_40: init %empty_tuple.type = call %Destroy.WithSelf.SelfDestruct.bound.loc13_40(%.loc13_40.2)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.bound.loc13_32: <bound method> = bound_method %.loc13_32.2, constants.%Destroy.WithSelf.SelfDestruct.db3fdb.2
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.call.loc13_32: init %empty_tuple.type = call %Destroy.WithSelf.SelfDestruct.bound.loc13_32(%.loc13_32.2)
|
||||
// CHECK:STDOUT: <elided>
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SubobjectDestroy.loc13_40.1(%self.param: ref %empty_struct_type) = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc13_40.1(%self.param: ref %empty_struct_type) = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc13_40.2(%self.param: ref %X) {
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SelfDestruct.loc13_40.1(%self.param: ref %empty_struct_type) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.Op.decl.5f94cf.1(%self.param)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.SubobjectDestroy.decl.763c71.1(%self.param)
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SubobjectDestroy.loc13_40.2(%self.param: ref %X) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc13_40.2(%self.param: ref %X) = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SelfDestruct.loc13_40.2(%self.param: ref %X) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.Op.decl.5f94cf.2(%self.param)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.SubobjectDestroy.decl.763c71.2(%self.param)
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
@@ -277,7 +363,7 @@ let n: {.x: ()} = {.x = ()} as {.x = ()};
|
||||
// CHECK:STDOUT: %.loc20_41.1: %tuple.type.359 = tuple_literal (%Make.call.loc20_32, %Make.call.loc20_40)
|
||||
// CHECK:STDOUT: %X.ref.loc20_47: type = name_ref X, file.%X.decl [concrete = constants.%X]
|
||||
// CHECK:STDOUT: %X.ref.loc20_50: type = name_ref X, file.%X.decl [concrete = constants.%X]
|
||||
// CHECK:STDOUT: %.loc20_51.1: %tuple.type.24b = tuple_literal (%X.ref.loc20_47, %X.ref.loc20_50) [concrete = constants.%tuple]
|
||||
// CHECK:STDOUT: %.loc20_51.1: %tuple.type.693 = tuple_literal (%X.ref.loc20_47, %X.ref.loc20_50) [concrete = constants.%tuple]
|
||||
// CHECK:STDOUT: %.loc20_51.2: type = converted %.loc20_51.1, constants.%tuple.type.359 [concrete = constants.%tuple.type.359]
|
||||
// CHECK:STDOUT: %.loc20_41.2: init %tuple.type.359 to %b.var = tuple_init (%Make.call.loc20_32, %Make.call.loc20_40)
|
||||
// CHECK:STDOUT: %.loc20_3: init %tuple.type.359 = converted %.loc20_41.1, %.loc20_41.2
|
||||
@@ -285,7 +371,7 @@ let n: {.x: ()} = {.x = ()} as {.x = ()};
|
||||
// CHECK:STDOUT: %.loc20_22.1: type = splice_block %.loc20_22.3 [concrete = constants.%tuple.type.359] {
|
||||
// CHECK:STDOUT: %X.ref.loc20_18: type = name_ref X, file.%X.decl [concrete = constants.%X]
|
||||
// CHECK:STDOUT: %X.ref.loc20_21: type = name_ref X, file.%X.decl [concrete = constants.%X]
|
||||
// CHECK:STDOUT: %.loc20_22.2: %tuple.type.24b = tuple_literal (%X.ref.loc20_18, %X.ref.loc20_21) [concrete = constants.%tuple]
|
||||
// CHECK:STDOUT: %.loc20_22.2: %tuple.type.693 = tuple_literal (%X.ref.loc20_18, %X.ref.loc20_21) [concrete = constants.%tuple]
|
||||
// CHECK:STDOUT: %.loc20_22.3: type = converted %.loc20_22.2, constants.%tuple.type.359 [concrete = constants.%tuple.type.359]
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %b: ref %tuple.type.359 = wrapper_binding b, %b.var
|
||||
@@ -293,19 +379,29 @@ let n: {.x: ()} = {.x = ()} as {.x = ()};
|
||||
// CHECK:STDOUT: %b.patt: %pattern_type.3ee = ref_binding_pattern b [concrete = constants.%b.patt]
|
||||
// CHECK:STDOUT: %b.var_patt: %pattern_type.3ee = var_pattern %b.patt [concrete = constants.%b.var_patt]
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.bound: <bound method> = bound_method %b.var, constants.%Destroy.WithSelf.Op.403171.3
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call %Destroy.WithSelf.Op.bound(%b.var)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.bound: <bound method> = bound_method %b.var, constants.%Destroy.WithSelf.SelfDestruct.db3fdb.3
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.call: init %empty_tuple.type = call %Destroy.WithSelf.SelfDestruct.bound(%b.var)
|
||||
// CHECK:STDOUT: <elided>
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc20(%self.param: ref %tuple.type.359) {
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SubobjectDestroy.loc20(%self.param: ref %tuple.type.359) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc20(%self.param: ref %tuple.type.359) = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SelfDestruct.loc20(%self.param: ref %tuple.type.359) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.Op.decl.5f94cf.3(%self.param)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.SubobjectDestroy.decl.763c71.3(%self.param)
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: --- identity.carbon
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: constants {
|
||||
// CHECK:STDOUT: type: type = facet_type <type> [concrete]
|
||||
// CHECK:STDOUT: %X: type = class_type @X [concrete]
|
||||
// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [concrete]
|
||||
// CHECK:STDOUT: %pattern_type.6cb: type = pattern_type %X [concrete]
|
||||
@@ -318,8 +414,52 @@ let n: {.x: ()} = {.x = ()} as {.x = ()};
|
||||
// CHECK:STDOUT: %Make: %Make.type = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %x.patt: %pattern_type.6cb = ref_binding_pattern x [concrete]
|
||||
// CHECK:STDOUT: %x.var_patt: %pattern_type.6cb = var_pattern %x.patt [concrete]
|
||||
// CHECK:STDOUT: %pattern_type.a96: type = pattern_type %empty_struct_type [concrete]
|
||||
// CHECK:STDOUT: %self.param_patt.52f: %pattern_type.a96 = ref_param_pattern [concrete]
|
||||
// CHECK:STDOUT: %self.patt.4b1: %pattern_type.a96 = wrapper_binding_pattern self, %self.param_patt.52f [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.1: type = fn_type @Destroy.WithSelf.SubobjectDestroy.loc24_3.1 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.d01daf.1: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.1 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.type.ef016f.1: type = fn_type @Destroy.WithSelf.Op.loc24_3.1 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.403171.1: %Destroy.WithSelf.Op.type.ef016f.1 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %self.param_patt.925: %pattern_type.6cb = ref_param_pattern [concrete]
|
||||
// CHECK:STDOUT: %self.patt.617: %pattern_type.6cb = wrapper_binding_pattern self, %self.param_patt.925 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.2: type = fn_type @Destroy.WithSelf.SubobjectDestroy.loc24_3.2 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.d01daf.2: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.2 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.type.ef016f.2: type = fn_type @Destroy.WithSelf.Op.loc24_3.2 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.403171.2: %Destroy.WithSelf.Op.type.ef016f.2 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.type.fbceb5.2: type = fn_type @Destroy.WithSelf.SelfDestruct.loc24_3.2 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.db3fdb.2: %Destroy.WithSelf.SelfDestruct.type.fbceb5.2 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: generated {
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.decl.763c71.1: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.1 = fn_decl @Destroy.WithSelf.SubobjectDestroy.loc24_3.1 [concrete = constants.%Destroy.WithSelf.SubobjectDestroy.d01daf.1] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.a96 = ref_param_pattern [concrete = constants.%self.param_patt.52f]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.a96 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.4b1]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %empty_struct_type = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %empty_struct_type = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.decl.5f94cf.1: %Destroy.WithSelf.Op.type.ef016f.1 = fn_decl @Destroy.WithSelf.Op.loc24_3.1 [concrete = constants.%Destroy.WithSelf.Op.403171.1] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.a96 = ref_param_pattern [concrete = constants.%self.param_patt.52f]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.a96 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.4b1]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %empty_struct_type = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %empty_struct_type = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.decl.763c71.2: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.2 = fn_decl @Destroy.WithSelf.SubobjectDestroy.loc24_3.2 [concrete = constants.%Destroy.WithSelf.SubobjectDestroy.d01daf.2] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.6cb = ref_param_pattern [concrete = constants.%self.param_patt.925]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.6cb = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.617]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %X = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %X = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.decl.5f94cf.2: %Destroy.WithSelf.Op.type.ef016f.2 = fn_decl @Destroy.WithSelf.Op.loc24_3.2 [concrete = constants.%Destroy.WithSelf.Op.403171.2] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.6cb = ref_param_pattern [concrete = constants.%self.param_patt.925]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.6cb = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.617]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %X = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %X = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Value(%n.param: %X) {
|
||||
@@ -365,21 +505,40 @@ let n: {.x: ()} = {.x = ()} as {.x = ()};
|
||||
// CHECK:STDOUT: %x.patt: %pattern_type.6cb = ref_binding_pattern x [concrete = constants.%x.patt]
|
||||
// CHECK:STDOUT: %x.var_patt: %pattern_type.6cb = var_pattern %x.patt [concrete = constants.%x.var_patt]
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.bound: <bound method> = bound_method %x.var, constants.%Destroy.WithSelf.Op.403171.2
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call %Destroy.WithSelf.Op.bound(%x.var)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.bound: <bound method> = bound_method %x.var, constants.%Destroy.WithSelf.SelfDestruct.db3fdb.2
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.call: init %empty_tuple.type = call %Destroy.WithSelf.SelfDestruct.bound(%x.var)
|
||||
// CHECK:STDOUT: <elided>
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SubobjectDestroy.loc24_3.1(%self.param: ref %empty_struct_type) = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc24_3.1(%self.param: ref %empty_struct_type) = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc24_3.2(%self.param: ref %X) {
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SelfDestruct.loc24_3.1(%self.param: ref %empty_struct_type) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.Op.decl.5f94cf.1(%self.param)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.SubobjectDestroy.decl.763c71.1(%self.param)
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SubobjectDestroy.loc24_3.2(%self.param: ref %X) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc24_3.2(%self.param: ref %X) = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SelfDestruct.loc24_3.2(%self.param: ref %X) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.Op.decl.5f94cf.2(%self.param)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.SubobjectDestroy.decl.763c71.2(%self.param)
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: --- overloaded.carbon
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: constants {
|
||||
// CHECK:STDOUT: type: type = facet_type <type> [concrete]
|
||||
// CHECK:STDOUT: %X: type = class_type @X [concrete]
|
||||
// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete]
|
||||
// CHECK:STDOUT: %empty_tuple: %empty_tuple.type = tuple_value () [concrete]
|
||||
|
||||
+164
-7
@@ -95,8 +95,10 @@ fn Use() {
|
||||
// CHECK:STDOUT: --- add_const.carbon
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: constants {
|
||||
// CHECK:STDOUT: type: type = facet_type <type> [concrete]
|
||||
// CHECK:STDOUT: %X: type = class_type @X [concrete]
|
||||
// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [concrete]
|
||||
// CHECK:STDOUT: %pattern_type.6cb: type = pattern_type %X [concrete]
|
||||
// CHECK:STDOUT: %Init.type: type = fn_type @Init [concrete]
|
||||
// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete]
|
||||
// CHECK:STDOUT: %Init: %Init.type = struct_value () [concrete]
|
||||
@@ -112,8 +114,72 @@ fn Use() {
|
||||
// CHECK:STDOUT: %reference.var: ref %const = var_storage file.%reference.var_patt [concrete]
|
||||
// CHECK:STDOUT: %addr.daa: %ptr.faf = addr_of %reference.var [concrete]
|
||||
// CHECK:STDOUT: %b.patt: %pattern_type.7f7 = value_binding_pattern b [concrete]
|
||||
// CHECK:STDOUT: %pattern_type.a96: type = pattern_type %empty_struct_type [concrete]
|
||||
// CHECK:STDOUT: %self.param_patt.52f: %pattern_type.a96 = ref_param_pattern [concrete]
|
||||
// CHECK:STDOUT: %self.patt.4b1: %pattern_type.a96 = wrapper_binding_pattern self, %self.param_patt.52f [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.1: type = fn_type @Destroy.WithSelf.SubobjectDestroy.loc14_3.1 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.d01daf.1: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.1 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.type.ef016f.1: type = fn_type @Destroy.WithSelf.Op.loc14_3.1 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.403171.1: %Destroy.WithSelf.Op.type.ef016f.1 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %self.param_patt.925: %pattern_type.6cb = ref_param_pattern [concrete]
|
||||
// CHECK:STDOUT: %self.patt.617: %pattern_type.6cb = wrapper_binding_pattern self, %self.param_patt.925 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.2: type = fn_type @Destroy.WithSelf.SubobjectDestroy.loc14_3.2 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.d01daf.2: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.2 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.type.ef016f.2: type = fn_type @Destroy.WithSelf.Op.loc14_3.2 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.403171.2: %Destroy.WithSelf.Op.type.ef016f.2 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %self.param_patt.b9b: %pattern_type.dfd = ref_param_pattern [concrete]
|
||||
// CHECK:STDOUT: %self.patt.a99: %pattern_type.dfd = wrapper_binding_pattern self, %self.param_patt.b9b [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.3: type = fn_type @Destroy.WithSelf.SubobjectDestroy.loc14_3.3 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.d01daf.3: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.3 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.type.ef016f.3: type = fn_type @Destroy.WithSelf.Op.loc14_3.3 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.403171.3: %Destroy.WithSelf.Op.type.ef016f.3 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.type.fbceb5.3: type = fn_type @Destroy.WithSelf.SelfDestruct.loc14_3.3 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.db3fdb.3: %Destroy.WithSelf.SelfDestruct.type.fbceb5.3 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: generated {
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.decl.763c71.1: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.1 = fn_decl @Destroy.WithSelf.SubobjectDestroy.loc14_3.1 [concrete = constants.%Destroy.WithSelf.SubobjectDestroy.d01daf.1] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.a96 = ref_param_pattern [concrete = constants.%self.param_patt.52f]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.a96 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.4b1]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %empty_struct_type = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %empty_struct_type = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.decl.5f94cf.1: %Destroy.WithSelf.Op.type.ef016f.1 = fn_decl @Destroy.WithSelf.Op.loc14_3.1 [concrete = constants.%Destroy.WithSelf.Op.403171.1] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.a96 = ref_param_pattern [concrete = constants.%self.param_patt.52f]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.a96 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.4b1]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %empty_struct_type = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %empty_struct_type = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.decl.763c71.2: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.2 = fn_decl @Destroy.WithSelf.SubobjectDestroy.loc14_3.2 [concrete = constants.%Destroy.WithSelf.SubobjectDestroy.d01daf.2] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.6cb = ref_param_pattern [concrete = constants.%self.param_patt.925]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.6cb = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.617]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %X = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %X = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.decl.5f94cf.2: %Destroy.WithSelf.Op.type.ef016f.2 = fn_decl @Destroy.WithSelf.Op.loc14_3.2 [concrete = constants.%Destroy.WithSelf.Op.403171.2] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.6cb = ref_param_pattern [concrete = constants.%self.param_patt.925]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.6cb = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.617]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %X = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %X = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.decl.763c71.3: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.3 = fn_decl @Destroy.WithSelf.SubobjectDestroy.loc14_3.3 [concrete = constants.%Destroy.WithSelf.SubobjectDestroy.d01daf.3] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.dfd = ref_param_pattern [concrete = constants.%self.param_patt.b9b]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.dfd = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.a99]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %const = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %const = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.decl.5f94cf.3: %Destroy.WithSelf.Op.type.ef016f.3 = fn_decl @Destroy.WithSelf.Op.loc14_3.3 [concrete = constants.%Destroy.WithSelf.Op.403171.3] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.dfd = ref_param_pattern [concrete = constants.%self.param_patt.b9b]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.dfd = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.a99]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %const = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %const = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Use() {
|
||||
@@ -179,20 +245,47 @@ fn Use() {
|
||||
// CHECK:STDOUT: name_binding_decl {
|
||||
// CHECK:STDOUT: %b.patt: %pattern_type.7f7 = value_binding_pattern b [concrete = constants.%b.patt]
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.bound: <bound method> = bound_method %i.var, constants.%Destroy.WithSelf.Op.403171.3
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call %Destroy.WithSelf.Op.bound(%i.var)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.bound: <bound method> = bound_method %i.var, constants.%Destroy.WithSelf.SelfDestruct.db3fdb.3
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.call: init %empty_tuple.type = call %Destroy.WithSelf.SelfDestruct.bound(%i.var)
|
||||
// CHECK:STDOUT: <elided>
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SubobjectDestroy.loc14_3.1(%self.param: ref %empty_struct_type) = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc14_3.1(%self.param: ref %empty_struct_type) = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc14_3.2(%self.param: ref %X) {
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SelfDestruct.loc14_3.1(%self.param: ref %empty_struct_type) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.Op.decl.5f94cf.1(%self.param)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.SubobjectDestroy.decl.763c71.1(%self.param)
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SubobjectDestroy.loc14_3.2(%self.param: ref %X) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc14_3.3(%self.param: ref %const) {
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc14_3.2(%self.param: ref %X) = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SelfDestruct.loc14_3.2(%self.param: ref %X) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.Op.decl.5f94cf.2(%self.param)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.SubobjectDestroy.decl.763c71.2(%self.param)
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SubobjectDestroy.loc14_3.3(%self.param: ref %const) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc14_3.3(%self.param: ref %const) = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SelfDestruct.loc14_3.3(%self.param: ref %const) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.Op.decl.5f94cf.3(%self.param)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.SubobjectDestroy.decl.763c71.3(%self.param)
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
@@ -204,6 +297,7 @@ fn Use() {
|
||||
// CHECK:STDOUT: --- remove_const.carbon
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: constants {
|
||||
// CHECK:STDOUT: type: type = facet_type <type> [concrete]
|
||||
// CHECK:STDOUT: %X: type = class_type @X [concrete]
|
||||
// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [concrete]
|
||||
// CHECK:STDOUT: %const: type = const_type %X [concrete]
|
||||
@@ -214,8 +308,52 @@ fn Use() {
|
||||
// CHECK:STDOUT: %i.patt: %pattern_type.6cb = ref_binding_pattern i [concrete]
|
||||
// CHECK:STDOUT: %i.var_patt: %pattern_type.6cb = var_pattern %i.patt [concrete]
|
||||
// CHECK:STDOUT: %v.patt: %pattern_type.6cb = value_binding_pattern v [concrete]
|
||||
// CHECK:STDOUT: %pattern_type.a96: type = pattern_type %empty_struct_type [concrete]
|
||||
// CHECK:STDOUT: %self.param_patt.52f: %pattern_type.a96 = ref_param_pattern [concrete]
|
||||
// CHECK:STDOUT: %self.patt.4b1: %pattern_type.a96 = wrapper_binding_pattern self, %self.param_patt.52f [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.1: type = fn_type @Destroy.WithSelf.SubobjectDestroy.loc12_3.1 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.d01daf.1: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.1 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.type.ef016f.1: type = fn_type @Destroy.WithSelf.Op.loc12_3.1 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.403171.1: %Destroy.WithSelf.Op.type.ef016f.1 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %self.param_patt.925: %pattern_type.6cb = ref_param_pattern [concrete]
|
||||
// CHECK:STDOUT: %self.patt.617: %pattern_type.6cb = wrapper_binding_pattern self, %self.param_patt.925 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.2: type = fn_type @Destroy.WithSelf.SubobjectDestroy.loc12_3.2 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.d01daf.2: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.2 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.type.ef016f.2: type = fn_type @Destroy.WithSelf.Op.loc12_3.2 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.403171.2: %Destroy.WithSelf.Op.type.ef016f.2 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.type.fbceb5.2: type = fn_type @Destroy.WithSelf.SelfDestruct.loc12_3.2 [concrete]
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.db3fdb.2: %Destroy.WithSelf.SelfDestruct.type.fbceb5.2 = struct_value () [concrete]
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: generated {
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.decl.763c71.1: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.1 = fn_decl @Destroy.WithSelf.SubobjectDestroy.loc12_3.1 [concrete = constants.%Destroy.WithSelf.SubobjectDestroy.d01daf.1] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.a96 = ref_param_pattern [concrete = constants.%self.param_patt.52f]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.a96 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.4b1]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %empty_struct_type = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %empty_struct_type = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.decl.5f94cf.1: %Destroy.WithSelf.Op.type.ef016f.1 = fn_decl @Destroy.WithSelf.Op.loc12_3.1 [concrete = constants.%Destroy.WithSelf.Op.403171.1] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.a96 = ref_param_pattern [concrete = constants.%self.param_patt.52f]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.a96 = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.4b1]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %empty_struct_type = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %empty_struct_type = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.decl.763c71.2: %Destroy.WithSelf.SubobjectDestroy.type.dfcbdb.2 = fn_decl @Destroy.WithSelf.SubobjectDestroy.loc12_3.2 [concrete = constants.%Destroy.WithSelf.SubobjectDestroy.d01daf.2] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.6cb = ref_param_pattern [concrete = constants.%self.param_patt.925]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.6cb = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.617]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %X = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %X = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.decl.5f94cf.2: %Destroy.WithSelf.Op.type.ef016f.2 = fn_decl @Destroy.WithSelf.Op.loc12_3.2 [concrete = constants.%Destroy.WithSelf.Op.403171.2] {
|
||||
// CHECK:STDOUT: %self.param_patt: %pattern_type.6cb = ref_param_pattern [concrete = constants.%self.param_patt.925]
|
||||
// CHECK:STDOUT: %self.patt: %pattern_type.6cb = wrapper_binding_pattern self, %self.param_patt [concrete = constants.%self.patt.617]
|
||||
// CHECK:STDOUT: } {
|
||||
// CHECK:STDOUT: %self.param: ref %X = ref_param call_param0
|
||||
// CHECK:STDOUT: %self: ref %X = wrapper_binding self, %self.param
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Use() {
|
||||
@@ -243,15 +381,33 @@ fn Use() {
|
||||
// CHECK:STDOUT: name_binding_decl {
|
||||
// CHECK:STDOUT: %v.patt: %pattern_type.6cb = value_binding_pattern v [concrete = constants.%v.patt]
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.bound: <bound method> = bound_method %i.var, constants.%Destroy.WithSelf.Op.403171.2
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call %Destroy.WithSelf.Op.bound(%i.var)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.bound: <bound method> = bound_method %i.var, constants.%Destroy.WithSelf.SelfDestruct.db3fdb.2
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SelfDestruct.call: init %empty_tuple.type = call %Destroy.WithSelf.SelfDestruct.bound(%i.var)
|
||||
// CHECK:STDOUT: <elided>
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SubobjectDestroy.loc12_3.1(%self.param: ref %empty_struct_type) = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc12_3.1(%self.param: ref %empty_struct_type) = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc12_3.2(%self.param: ref %X) {
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SelfDestruct.loc12_3.1(%self.param: ref %empty_struct_type) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.Op.decl.5f94cf.1(%self.param)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.SubobjectDestroy.decl.763c71.1(%self.param)
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SubobjectDestroy.loc12_3.2(%self.param: ref %X) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc12_3.2(%self.param: ref %X) = "no_op";
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: fn @Destroy.WithSelf.SelfDestruct.loc12_3.2(%self.param: ref %X) {
|
||||
// CHECK:STDOUT: !entry:
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.Op.decl.5f94cf.2(%self.param)
|
||||
// CHECK:STDOUT: %Destroy.WithSelf.SubobjectDestroy.call: init %empty_tuple.type = call generated.%Destroy.WithSelf.SubobjectDestroy.decl.763c71.2(%self.param)
|
||||
// CHECK:STDOUT: return
|
||||
// CHECK:STDOUT: }
|
||||
// CHECK:STDOUT:
|
||||
@@ -263,6 +419,7 @@ fn Use() {
|
||||
// CHECK:STDOUT: --- unsafe_remove_const.carbon
|
||||
// CHECK:STDOUT:
|
||||
// CHECK:STDOUT: constants {
|
||||
// CHECK:STDOUT: type: type = facet_type <type> [concrete]
|
||||
// CHECK:STDOUT: %X: type = class_type @X [concrete]
|
||||
// CHECK:STDOUT: %const: type = const_type %X [concrete]
|
||||
// CHECK:STDOUT: %ptr.faf: type = ptr_type %const [concrete]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user