mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 20:20:10 +01:00
Compare commits
118
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
733b76efea | ||
|
|
69d745c6d7 | ||
|
|
d6a741f208 | ||
|
|
1e3906177c | ||
|
|
5e2f693db1 | ||
|
|
1cc699ddda | ||
|
|
ad0a4ea8a4 | ||
|
|
f91990aa87 | ||
|
|
e7626f46cc | ||
|
|
af04d08965 | ||
|
|
a6061d975c | ||
|
|
896338d281 | ||
|
|
d8fe95cccb | ||
|
|
df33276f6b | ||
|
|
327cb2396a | ||
|
|
1d5113649b | ||
|
|
cdfa57f230 | ||
|
|
46f46a538d | ||
|
|
114cf401c2 | ||
|
|
7c94878c10 | ||
|
|
82fad29028 | ||
|
|
26ddada3ae | ||
|
|
bab35c114e | ||
|
|
96529e16bd | ||
|
|
43867a678b | ||
|
|
5b1de7633c | ||
|
|
f31e1685fd | ||
|
|
3cdb159067 | ||
|
|
12d7574636 | ||
|
|
cc5a42691e | ||
|
|
19efec93be | ||
|
|
3db691ecef | ||
|
|
6cc08ae6e6 | ||
|
|
ab977ee04d | ||
|
|
be0c07dc7e | ||
|
|
b79d9adeca | ||
|
|
b74e0d1260 | ||
|
|
9f1a0c816c | ||
|
|
6f02354d62 | ||
|
|
0635f4628f | ||
|
|
d1dc8e820d | ||
|
|
e1f30669af | ||
|
|
f483a28f2f | ||
|
|
cc4fd39238 | ||
|
|
013a417ea5 | ||
|
|
8fd4156616 | ||
|
|
05ba1d7356 | ||
|
|
8e0d856725 | ||
|
|
ea409f7cbf | ||
|
|
fed9e8c878 | ||
|
|
451b50a3ad | ||
|
|
912825feaf | ||
|
|
81ed4d829d | ||
|
|
9266ced4e3 | ||
|
|
1fa7a64cd4 | ||
|
|
562b423830 | ||
|
|
6f0ec37a8b | ||
|
|
0851d657c8 | ||
|
|
07afa07127 | ||
|
|
f7a767a77b | ||
|
|
be283a0744 | ||
|
|
cd6ab7ce8e | ||
|
|
0075d530b9 | ||
|
|
5b7c908e8b | ||
|
|
2bbcfa5e1a | ||
|
|
dfac728571 | ||
|
|
8f3b057179 | ||
|
|
17180558e5 | ||
|
|
39eac6f277 | ||
|
|
2787089247 | ||
|
|
3578dd6b91 | ||
|
|
8b59e85b16 | ||
|
|
47e9d62fd5 | ||
|
|
f4260feee4 | ||
|
|
aec2534e9d | ||
|
|
2e0d9dc709 | ||
|
|
396756c151 | ||
|
|
bf6a14ac39 | ||
|
|
9ed045ec25 | ||
|
|
2318294eb5 | ||
|
|
d9b901394b | ||
|
|
181a592b8c | ||
|
|
1ef35e8299 | ||
|
|
aa8e96ac72 | ||
|
|
5503f643c6 | ||
|
|
899e54de36 | ||
|
|
a2ba7f1262 | ||
|
|
2b1fe7c292 | ||
|
|
a0416a1250 | ||
|
|
c68c4007ab | ||
|
|
1f7d8e4675 | ||
|
|
557039648b | ||
|
|
786e02cb3e | ||
|
|
262e24a2a0 | ||
|
|
18f87e4f79 | ||
|
|
85da6cae01 | ||
|
|
d6be20641c | ||
|
|
4a0c1ddd8e | ||
|
|
5ae3629e49 | ||
|
|
25b85a55ae | ||
|
|
15dabbfb0d | ||
|
|
25793358c3 | ||
|
|
37b238fa28 | ||
|
|
311670c84a | ||
|
|
965879a9a9 | ||
|
|
415cd6f8f0 | ||
|
|
7345f4e860 | ||
|
|
29a8b315d3 | ||
|
|
e0305684b0 | ||
|
|
2af5f971da | ||
|
|
f9e1806c4a | ||
|
|
e0c6800ab3 | ||
|
|
a345a74145 | ||
|
|
86d05b598b | ||
|
|
7b6e3dfbb0 | ||
|
|
17657d0586 | ||
|
|
3ef991466d | ||
|
|
44f2a68ee0 |
@@ -0,0 +1,83 @@
|
||||
---
|
||||
name: Bazel usage
|
||||
description:
|
||||
Instructions for using Bazel or Bazelisk to build, test, and debug in the
|
||||
Carbon repository.
|
||||
---
|
||||
|
||||
# Bazel usage
|
||||
|
||||
<!--
|
||||
Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
||||
Exceptions. See /LICENSE for license information.
|
||||
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
-->
|
||||
|
||||
This skill documents how best to use Bazel when building, testing, or
|
||||
manipulating the Carbon repository's Bazel in any way.
|
||||
|
||||
## Bazel wrappers
|
||||
|
||||
Carbon uses Bazel for its build system. To ensure consistent versions, the
|
||||
project uses Bazelisk.
|
||||
|
||||
> [!IMPORTANT] Always use `bazelisk` whenever you want to run Bazel. Never run
|
||||
> `bazel` directly in the Carbon project. Anything you want to do with `bazel`
|
||||
> can be done with the `bazelisk` command instead.
|
||||
|
||||
- **Bazelisk**: Try to use `bazelisk` in your existing `$PATH` if available.
|
||||
- **`run_bazelisk.py`**: If `bazelisk` isn't available, use
|
||||
`./scripts/run_bazelisk.py` to run bazelisk without it being installed.
|
||||
|
||||
## Essential commands
|
||||
|
||||
### Building
|
||||
|
||||
- **Build all**: `bazelisk build //...`
|
||||
- **Build toolchain**: `bazelisk build //toolchain/...`
|
||||
- **Build specific target**: `bazelisk build //toolchain:carbon`
|
||||
|
||||
### Testing
|
||||
|
||||
- **Test all**: `bazelisk test //...:all`
|
||||
- **Test toolchain**: `bazelisk test //toolchain/...`
|
||||
- **Test examples**: `bazelisk test //examples/...`
|
||||
|
||||
> [!TIP] Running all of the tests can be slow, so try to narrowly test the
|
||||
> immediately relevant parts of the project first, and only expand coverage as
|
||||
> necessary to be confident in the changes.
|
||||
|
||||
> [!TIP] For specialized instructions on testing and developing the Carbon
|
||||
> toolchain, consult these skills:
|
||||
>
|
||||
> - [Toolchain tests](/.agents/skills/toolchain_tests/SKILL.md): For
|
||||
> authoring, structuring, and running `file_test` tests.
|
||||
> - [Toolchain development](/.agents/skills/toolchain_development/SKILL.md):
|
||||
> For architecture, essential commands, and debugging the toolchain.
|
||||
|
||||
### Running binaries built by Bazel
|
||||
|
||||
> [!IMPORTANT] Always manually run binaries built by Bazel using the
|
||||
> `bazelisk run` command. Never run the binary directly from `bazel-bin/`.
|
||||
|
||||
You can run the Carbon driver or command line directly via Bazel:
|
||||
|
||||
- `bazelisk run //toolchain -- compile --phase=parse toolchain/parse/testdata/basics/empty.carbon`
|
||||
|
||||
## Advanced configurations
|
||||
|
||||
### AddressSanitizer (ASan)
|
||||
|
||||
To enable ASan for local testing:
|
||||
|
||||
- Pass `--config=asan`: `bazelisk test --config=asan //...`
|
||||
|
||||
## Common pitfalls and troubleshooting
|
||||
|
||||
### `bazel clean`
|
||||
|
||||
Changes to packages installed on your system (like changing LLVM versions or
|
||||
installing `libc++`) may not be noticed by Bazel.
|
||||
|
||||
- Run `bazelisk clean` to force cached state to be rebuilt when environment
|
||||
changes occur.
|
||||
@@ -0,0 +1,171 @@
|
||||
---
|
||||
name: GitHub CLI usage
|
||||
description:
|
||||
Instructions for using the `gh` command to query and inspect GitHub state
|
||||
safely.
|
||||
---
|
||||
|
||||
# GitHub CLI usage
|
||||
|
||||
<!--
|
||||
Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
||||
Exceptions. See /LICENSE for license information.
|
||||
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
-->
|
||||
|
||||
This skill provides instructions for using the GitHub CLI (`gh`) to query,
|
||||
inspect, and search GitHub state (issues, pull requests, repositories) for the
|
||||
Carbon project.
|
||||
|
||||
## Safety First: Read-Only Usage
|
||||
|
||||
> [!IMPORTANT] AI assistants MUST NOT use the `gh` tool to modify any GitHub
|
||||
> project state. Do NOT run commands that create, edit, delete, label, comment
|
||||
> on, or merge issues, pull requests, releases, or any other resources.
|
||||
|
||||
### Allowed Verbs
|
||||
|
||||
- `list`
|
||||
- `view`
|
||||
- `search`
|
||||
- `status`
|
||||
- `api` (Only with `GET` requests)
|
||||
|
||||
### Prohibited Verbs
|
||||
|
||||
- `create`
|
||||
- `edit`
|
||||
- `delete`
|
||||
- `merge`
|
||||
- `reopen`
|
||||
- `close`
|
||||
- `comment`
|
||||
- `label`
|
||||
|
||||
## Repository Configuration
|
||||
|
||||
The `gh` tool interacts with a default repository when run within a local check
|
||||
out. For this project, the default repository is expected to be
|
||||
`carbon-language/carbon-lang`.
|
||||
|
||||
### Verifying Default Repository
|
||||
|
||||
To verify the current default repository configuration:
|
||||
|
||||
```bash
|
||||
gh repo view
|
||||
```
|
||||
|
||||
The output should indicate the repository is `carbon-language/carbon-lang`.
|
||||
|
||||
### Correcting Misconfigurations
|
||||
|
||||
If the default repository is misconfigured (for example, pointing to a personal
|
||||
fork or a different repository), the human operator must correct it.
|
||||
|
||||
> [!IMPORTANT] AI Assistants MUST NOT attempt to mutate `gh` configuration or
|
||||
> run commands that change the default repository (such as
|
||||
> `gh repository set-default`).
|
||||
|
||||
Instruct the human operator to run the following command to select the correct
|
||||
default repository:
|
||||
|
||||
```bash
|
||||
gh repo set-default
|
||||
```
|
||||
|
||||
The operator will be prompted to select the correct repository (e.g.,
|
||||
`carbon-language/carbon-lang`) from the available remotes.
|
||||
|
||||
## Common Query Commands
|
||||
|
||||
### Issues
|
||||
|
||||
- **List issues**: `gh issue list`
|
||||
- **View specific issue**: `gh issue view <number>`
|
||||
- **Search issues**: `gh issue search "<query>"`
|
||||
- Example: `gh issue search "crash" --state open`
|
||||
|
||||
### Pull Requests
|
||||
|
||||
- **List PRs**: `gh pr list`
|
||||
- **View specific PR**: `gh pr view <number>`
|
||||
- **View PR diff**: `gh pr diff <number>`
|
||||
- **Check PR status**: `gh pr status`
|
||||
|
||||
### Search
|
||||
|
||||
- **Search code**: `gh search code "<query>"`
|
||||
- **Search repositories**: `gh search repos "<query>"`
|
||||
|
||||
## Advanced Usage: GitHub API
|
||||
|
||||
For queries that are not supported by standard `gh` commands, you can use the
|
||||
`gh api` command to query the GitHub REST or GraphQL APIs.
|
||||
|
||||
### REST API
|
||||
|
||||
Query the REST API using paths relative to the API root.
|
||||
|
||||
- **List contributors**:
|
||||
|
||||
```bash
|
||||
gh api repos/carbon-language/carbon-lang/contributors
|
||||
```
|
||||
|
||||
- **List issue comments**:
|
||||
|
||||
```bash
|
||||
gh api repos/carbon-language/carbon-lang/issues/<issue_number>/comments
|
||||
```
|
||||
|
||||
### GraphQL API
|
||||
|
||||
For complex queries, use GraphQL to fetch exactly the data needed.
|
||||
|
||||
- **Get repository information**:
|
||||
|
||||
```bash
|
||||
gh api graphql -f query='
|
||||
query {
|
||||
repository(owner: "carbon-language", name: "carbon-lang") {
|
||||
description
|
||||
stargazerCount
|
||||
}
|
||||
}
|
||||
'
|
||||
```
|
||||
|
||||
### Pagination
|
||||
|
||||
Use the `--paginate` flag to automatically fetch all pages of results.
|
||||
|
||||
```bash
|
||||
gh api --paginate repos/carbon-language/carbon-lang/issues
|
||||
```
|
||||
|
||||
### Filtering and Formatting
|
||||
|
||||
Use `--json` to request JSON output, and `--jq` or `--template` to filter or
|
||||
format the results.
|
||||
|
||||
- **List PR titles and authors**:
|
||||
|
||||
```bash
|
||||
gh pr list --json title,author --jq '.[] | "\(.title) by \(.author.login)"'
|
||||
```
|
||||
|
||||
- **Format with Go templates**:
|
||||
|
||||
```bash
|
||||
gh issue list --template '{{range .}}{{.number}} - {{.title}}{{"\n"}}{{end}}'
|
||||
```
|
||||
|
||||
## Documentation References
|
||||
|
||||
- **GitHub CLI Manual**:
|
||||
[cli.github.com/manual](https://cli.github.com/manual/)
|
||||
- **GitHub REST API Documentation**:
|
||||
[docs.github.com/en/rest](https://docs.github.com/en/rest)
|
||||
- **GitHub GraphQL API Documentation**:
|
||||
[docs.github.com/en/graphql](https://docs.github.com/en/graphql)
|
||||
@@ -0,0 +1,95 @@
|
||||
---
|
||||
name: Accessing GitHub issues
|
||||
description:
|
||||
Instructions for safely viewing and accessing GitHub issues by way of
|
||||
command line.
|
||||
---
|
||||
|
||||
# Accessing GitHub issues
|
||||
|
||||
<!--
|
||||
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
|
||||
-->
|
||||
|
||||
This skill provides instructions for AI assistants on how to access and view
|
||||
GitHub issues. Agents should strongly prefer using the command line `gh` tool to
|
||||
access and view the contents of issues rather than viewing their contents by way
|
||||
of a web browser.
|
||||
|
||||
## Safety First
|
||||
|
||||
> [!IMPORTANT] AI assistants MUST NOT modify any GitHub issue state. Only use
|
||||
> read-only access commands like `view` or `list`. Do NOT comment, edit, create,
|
||||
> close, or delete issues.
|
||||
|
||||
## Accessing Issues
|
||||
|
||||
Agents must use this skill to access issues regardless of how they are mentioned
|
||||
(for example, by URL or by issue number).
|
||||
|
||||
### Basic View
|
||||
|
||||
To view an issue in the current default repository (expected to be Carbon):
|
||||
|
||||
```bash
|
||||
gh issue view <issue_number>
|
||||
```
|
||||
|
||||
### Including Full Context (All Comments)
|
||||
|
||||
To ensure the view includes the entire context of the issue, always include the
|
||||
`--comments` flag to dump all comments:
|
||||
|
||||
```bash
|
||||
gh issue view <issue_number> --comments
|
||||
```
|
||||
|
||||
> [!TIP] If the issue is extremely large and comments are truncated, or you need
|
||||
> to process comments programmatically, use the JSON output with `jq`:
|
||||
>
|
||||
> ```bash
|
||||
> gh issue view <issue_number> --json comments --jq '.comments[].body'
|
||||
> ```
|
||||
|
||||
### Accessing Issues in Other Repositories
|
||||
|
||||
To view an issue in another repository (for example, LLVM), use the `-R` or
|
||||
`--repo` flag to specify the repository:
|
||||
|
||||
```bash
|
||||
gh issue view <issue_number> -R <owner>/<repo> --comments
|
||||
```
|
||||
|
||||
Examples:
|
||||
|
||||
- **LLVM Issue**:
|
||||
|
||||
```bash
|
||||
gh issue view 5678 -R llvm/llvm-project --comments
|
||||
```
|
||||
|
||||
- **Carbon Issue (Explicit)**:
|
||||
|
||||
```bash
|
||||
gh issue view 1234 -R carbon-language/carbon-lang --comments
|
||||
```
|
||||
|
||||
## Mentions via URL
|
||||
|
||||
If an issue is mentioned via URL, parse the URL to extract the repository owner,
|
||||
repository name, and issue number.
|
||||
|
||||
- **URL pattern**: `https://github.com/<owner>/<repo>/issues/<number>`
|
||||
- **Extraction**:
|
||||
- Host: `github.com`
|
||||
- Owner: `<owner>`
|
||||
- Repo: `<repo>`
|
||||
- Number: `<number>`
|
||||
|
||||
Run the command specifying the repository:
|
||||
|
||||
```bash
|
||||
gh issue view <number> -R <owner>/<repo> --comments
|
||||
```
|
||||
@@ -1,18 +1,18 @@
|
||||
---
|
||||
name: Summarize testdata changes
|
||||
description:
|
||||
Instructions for summarizing changes to Carbon testdata files
|
||||
(`toolchain/*/testdata`).
|
||||
---
|
||||
|
||||
# Summarize 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
|
||||
-->
|
||||
|
||||
---
|
||||
|
||||
name: Summarize testdata changes description: Instructions for summarizing
|
||||
changes to Carbon testdata files (`toolchain/*/testdata`).
|
||||
|
||||
---
|
||||
|
||||
# Summarize testdata changes
|
||||
|
||||
This skill provides instructions for creating a comprehensive report summarizing
|
||||
changes to Carbon testdata files (`toolchain/*/testdata`) and associating them
|
||||
with related code changes.
|
||||
|
||||
@@ -52,9 +52,13 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
### Updating test data
|
||||
|
||||
Carbon tests often use `file_test` (for example,
|
||||
`//toolchain/testing/file_test`). If you change compiler behavior, you likely
|
||||
need to update expected test outputs. **Do not manually edit thousands of lines
|
||||
of expected output.** Use the script:
|
||||
`//toolchain/testing/file_test`). For detailed guidelines on authoring tests,
|
||||
including file splits, naming conventions (`fail_`, `todo_`), and generating
|
||||
minimal output with SemIR dumps, please refer to the **Toolchain tests** skill.
|
||||
|
||||
If you change compiler behavior, you likely need to update expected test
|
||||
outputs. **Do not manually edit thousands of lines of expected output.** Use the
|
||||
script:
|
||||
|
||||
```bash
|
||||
./toolchain/autoupdate_testdata.py
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
---
|
||||
name: Toolchain tests
|
||||
description:
|
||||
Instructions for authoring, structuring, and running toolchain tests using
|
||||
the file_test infrastructure.
|
||||
---
|
||||
|
||||
# Toolchain tests
|
||||
|
||||
<!--
|
||||
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 provides guidelines and patterns for creating and updating tests for
|
||||
the Carbon toolchain, especially file tests in `toolchain/*/testdata/` (for
|
||||
example, `toolchain/check/testdata/`).
|
||||
|
||||
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.
|
||||
|
||||
## Structure and Authoring
|
||||
|
||||
### File Layout and Headers
|
||||
|
||||
Test files must start with the standard Carbon license, followed by
|
||||
configuration comments. Separate sections with blank comment lines (`//`).
|
||||
|
||||
```carbon
|
||||
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
||||
// Exceptions. See /LICENSE for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
// INCLUDE-FILE: toolchain/testing/testdata/min_prelude/...
|
||||
//
|
||||
// AUTOUPDATE
|
||||
```
|
||||
|
||||
- `// AUTOUPDATE` is mandatory for files using CHECK markers.
|
||||
- `// TIP:` lines are automatically generated by the autoupdater. You do not
|
||||
need to hand-write them. It is harmless to add them, but the script will
|
||||
handle it.
|
||||
|
||||
### Minimized Preludes
|
||||
|
||||
When writing tests entirely unrelated to the Core package, specify a minimal
|
||||
prelude file using `// INCLUDE-FILE`. Usually, include
|
||||
`toolchain/testing/testdata/min_prelude/` scripts, such as `int.carbon` or
|
||||
`primitives.carbon`. This significantly speeds up execution and minimizes STDOUT
|
||||
noise.
|
||||
|
||||
### Split Tests and `[[@TEST_NAME]]`
|
||||
|
||||
A single physical file can test multiple scenarios using split constraints:
|
||||
|
||||
```carbon
|
||||
// --- passing_case.carbon
|
||||
library "[[@TEST_NAME]]";
|
||||
|
||||
// ...
|
||||
|
||||
// --- fail_bad_case.carbon
|
||||
library "[[@TEST_NAME]]";
|
||||
// ...
|
||||
```
|
||||
|
||||
- Use `library "[[@TEST_NAME]]";` in each split when necessary to prevent name
|
||||
conflicts or redefining the default library.
|
||||
- Exactly `[[@TEST_NAME]]` (including the brackets) should be used. The test
|
||||
infrastructure automatically replaces it with the split's filename minus
|
||||
`todo_` and `fail_` prefixes.
|
||||
- **Do not put code that is expected to pass and code that is expected to fail
|
||||
into the same split.** Validation relies on non-failing splits producing
|
||||
absolutely no errors and failing splits producing the correct compiler
|
||||
errors independently.
|
||||
|
||||
### File Prefixing: `fail_` and `todo_`
|
||||
|
||||
Expected failures must be differentiated from unexpected failures (and from
|
||||
bugs). Include prefixes to name individual split files or the main test:
|
||||
|
||||
- `fail_...`: The test should and does produce compiler errors.
|
||||
- `todo_fail_...`: The test should produce errors but currently does not.
|
||||
- `fail_todo_...`: The test does produce errors or crashes, but it shouldn't
|
||||
(or produces the wrong errors or otherwise misbehaves with errors).
|
||||
- `todo_...`: The test has some incorrect behavior, but doesn't produce errors
|
||||
currently, and shouldn't.
|
||||
|
||||
**Main File Naming**: The main test file (and any split-files) must have a
|
||||
`fail_` prefix if they have an associated error. **Exception**: The main file
|
||||
may omit `fail_` if it contains a least one split that has a `fail_` prefix.
|
||||
|
||||
Both the `fail_` and `todo_` prefixes are stripped from filename properties like
|
||||
`[[@TEST_NAME]]`.
|
||||
|
||||
### Test Code Comments
|
||||
|
||||
- **No agent thinking:** Do not include comments describing your reasoning or
|
||||
"train of thought" (for example, "Wait, but...") inside the test files. Any
|
||||
comments left in tests should be concise and describe what the test _itself_
|
||||
is validating for human readers.
|
||||
|
||||
## SemIR Dumps and Minimizing Output
|
||||
|
||||
Limit STDOUT checks to the logic under test. Always use `//@dump-sem-ir-begin`
|
||||
and `//@dump-sem-ir-end` around the specific declarations/blocks where SemIR
|
||||
output is desired. Only use these markers and **not**
|
||||
`--dump-sem-ir-ranges=if-present` or similar extra args—new tests use
|
||||
`//@dump-sem-ir...` to naturally filter output to the highlighted segments based
|
||||
on the default behavior.
|
||||
|
||||
```carbon
|
||||
//@dump-sem-ir-begin
|
||||
fn F(x:? form(ref i32));
|
||||
//@dump-sem-ir-end
|
||||
```
|
||||
|
||||
## Creating/Updating the Output
|
||||
|
||||
AI tools should **never** hand-write or manually touch `// CHECK:STDOUT:` or
|
||||
`// CHECK:STDERR:` comments.
|
||||
|
||||
Write your Carbon test code, headers, and `// AUTOUPDATE` then run the test
|
||||
updater:
|
||||
|
||||
```bash
|
||||
./toolchain/autoupdate_testdata.py toolchain/PATH/TO/YOUR/TEST.carbon
|
||||
```
|
||||
|
||||
Review the updated test outputs (for example, by way of `git diff`). Ensure
|
||||
logic paths are correctly tested rather than producing massive boilerplate
|
||||
blocks.
|
||||
@@ -38,11 +38,9 @@ common:non-fatal-checks --per_file_copt=common/check_internal.cpp@-DCARBON_NON_F
|
||||
# enable use of the target config here to make our build and tests more
|
||||
# efficient, see the documentation in //bazel/carbon_rules/BUILD for details.
|
||||
common --flag_alias=use_target_config_carbon_rules=//bazel/carbon_rules:use_target_config_carbon_rules
|
||||
common --flag_alias=use_target_config_runtimes_builder=//toolchain/driver:use_target_config_runtimes_builder
|
||||
# Bazel doesn't track what commands the flag_alias is valid for, so we can't use
|
||||
# common here.
|
||||
build --use_target_config_carbon_rules
|
||||
build --use_target_config_runtimes_builder
|
||||
|
||||
# Default to using a disk cache to minimize re-building LLVM and Clang which we
|
||||
# try to avoid updating too frequently to minimize rebuild cost. The location
|
||||
@@ -87,12 +85,12 @@ common --define=absl=1
|
||||
# Enable TCMalloc on Linux in optimized builds.
|
||||
common --custom_malloc=//bazel/malloc:tcmalloc_if_linux_opt
|
||||
|
||||
# Configuration for enabling Address Sanitizer. Note that this is enabled by
|
||||
# default for fastbuild. The config is provided to enable ASan even in
|
||||
# optimized or other build configurations. Note that ASan and TCMalloc are
|
||||
# Configuration for enabling Address Sanitizer. Note that ASan and TCMalloc are
|
||||
# incompatible so this explicitly forces the system malloc.
|
||||
common:asan --features=asan
|
||||
common:asan --custom_malloc=@bazel_tools//tools/cpp:malloc
|
||||
# Also double the test timeouts for ASan to improve their consistency.
|
||||
test:asan --test_timeout=120,600,1800,-1
|
||||
|
||||
# Configuration for enabling LibFuzzer (along with ASan).
|
||||
common:fuzzer --features=fuzzer
|
||||
@@ -123,6 +121,11 @@ common:linux --define=pfm=1
|
||||
# Disables `actions.declare_symlink`. Done for cross-environment support.
|
||||
common --allow_unresolved_symlinks=false
|
||||
|
||||
# Removes the leading `/proc/self/cwd/` from file paths in the debug info. Some
|
||||
# tools like VS Code don't understand `/proc/self/cwd` in places like terminal
|
||||
# stack dumps, but do understand paths relative to the workspace root.
|
||||
common --copt=-fdebug-prefix-map=/proc/self/cwd=
|
||||
|
||||
# Allow users to override any of the flags desired by importing a user-specific
|
||||
# RC file here if present.
|
||||
try-import %workspace%/user.bazelrc
|
||||
|
||||
@@ -11,6 +11,7 @@ DerivePointerAlignment: 'false'
|
||||
ExperimentalAutoDetectBinPacking: 'false'
|
||||
FixNamespaceComments: 'true'
|
||||
InsertBraces: 'true'
|
||||
InsertTrailingCommas: None
|
||||
PointerAlignment: Left
|
||||
# We abuse control macros for formatting other kinds of macros.
|
||||
SpaceBeforeParens: ControlStatementsExceptControlMacros
|
||||
@@ -26,3 +27,8 @@ StatementMacros: ['ABSTRACT']
|
||||
QualifierAlignment: Custom
|
||||
QualifierOrder:
|
||||
[inline, static, friend, constexpr, const, volatile, restrict, type]
|
||||
Macros:
|
||||
# These macros can contain variable declarations, so clang-format needs to
|
||||
# "see through" them in order to format them correctly.
|
||||
- CARBON_ASSIGN_OR_RETURN(x)=x
|
||||
- CARBON_KIND(x)=x
|
||||
|
||||
@@ -70,6 +70,13 @@ runs:
|
||||
build --remote_cache=https://storage.googleapis.com/carbon-builds-github-v${CACHE_VERSION}
|
||||
build --remote_download_outputs=minimal
|
||||
|
||||
# Allow passing targets that are incompatible so that our explicit
|
||||
# target lists work more like //... wild card patterns in CI. In CI,
|
||||
# we're using explicit target lists to prune to a minimal set of
|
||||
# dependencies, and so skipping incompatible targets is the expected
|
||||
# behavior.
|
||||
build --skip_incompatible_explicit_targets
|
||||
|
||||
# We import a special key into every action in order to key the Bazel
|
||||
# remote cache in a way that avoids collisions between different
|
||||
# runners. Anything that might change the system external to Bazel but
|
||||
|
||||
@@ -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
|
||||
|
||||
name: Check Dependent Label
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, synchronize, labeled, unlabeled]
|
||||
|
||||
# This workflow runs as `pull_request_target` so that the check can't be
|
||||
# disabled or bypassed by a the PR, but it doesn't need any permissions.
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
check_label:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
|
||||
with:
|
||||
disable-sudo: true
|
||||
egress-policy: block
|
||||
# prettier-ignore
|
||||
allowed-endpoints: >
|
||||
api.github.com:443
|
||||
|
||||
- name: Check for 'dependent' label
|
||||
run: |
|
||||
if [[ "${{ contains(github.event.pull_request.labels.*.name, 'dependent') }}" == "true" ]]; then
|
||||
echo "PR has 'dependent' label. Blocking merge."
|
||||
exit 1
|
||||
fi
|
||||
echo "PR does not have 'dependent' label."
|
||||
@@ -75,10 +75,6 @@ jobs:
|
||||
if: steps.filter.outputs.has_cpp == 'true'
|
||||
run: ./scripts/create_compdb.py
|
||||
|
||||
- name: Build deps for clangd-tidy
|
||||
if: steps.filter.outputs.has_cpp == 'true'
|
||||
run: ./scripts/run_bazel.py build //scripts:deps_for_clangd_tidy
|
||||
|
||||
- name: Install clangd-tidy
|
||||
if: steps.filter.outputs.has_cpp == 'true'
|
||||
run: pip install clangd-tidy==1.1.0.post2
|
||||
|
||||
@@ -93,7 +93,7 @@ jobs:
|
||||
test -c opt --stamp --remote_download_toplevel \
|
||||
--pre_release=nightly --nightly_date=${{ env.nightly_date }} \
|
||||
//toolchain \
|
||||
//toolchain/install:carbon_toolchain_tar_gz_rule \
|
||||
//toolchain/install:carbon_toolchain_tar_gz \
|
||||
//toolchain/install:carbon_toolchain_tar_gz_test
|
||||
|
||||
- name: Extract the release version
|
||||
|
||||
@@ -28,8 +28,7 @@ jobs:
|
||||
# Only generate suggestions if pre-commit for a PR failed.
|
||||
if: |
|
||||
github.event.workflow_run.conclusion == 'failure' &&
|
||||
github.event.workflow_run.event == 'pull_request' &&
|
||||
github.actor != 'jonmeow'
|
||||
github.event.workflow_run.event == 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
|
||||
@@ -22,13 +22,30 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name:
|
||||
Testing ${{ matrix.config.name != 'Default' && format('({0})',
|
||||
matrix.config.name) || '' }} (${{ matrix.runner }})
|
||||
strategy:
|
||||
matrix:
|
||||
# Test a recent version of each supported OS.
|
||||
runner: ['ubuntu-22.04', 'macos-14']
|
||||
build_mode: [fastbuild, opt]
|
||||
# Create a synthetic matrix dimension with the event name for filtering.
|
||||
event: ['${{ github.event_name }}']
|
||||
config:
|
||||
- name: 'Default'
|
||||
flags: ''
|
||||
- name: 'Opt'
|
||||
flags: '-c opt'
|
||||
- name: 'ASan'
|
||||
flags: '--config=asan'
|
||||
exclude:
|
||||
- runner: 'macos-14'
|
||||
config: { name: 'ASan', flags: '--config=asan' }
|
||||
- event: 'pull_request'
|
||||
config: { name: 'ASan', flags: '--config=asan' }
|
||||
- event: 'merge_group'
|
||||
config: { name: 'ASan', flags: '--config=asan' }
|
||||
runs-on: ${{ matrix.runner }}
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
|
||||
@@ -74,7 +91,7 @@ jobs:
|
||||
targets_file: ${{ runner.temp }}/targets
|
||||
|
||||
# Build and run just the tests impacted by the PR or merge group.
|
||||
- name: Test (${{ matrix.build_mode }})
|
||||
- name: Test (${{ matrix.config.name }})
|
||||
if: steps.test-setup.outputs.has_code == 'true'
|
||||
shell: bash
|
||||
env:
|
||||
@@ -85,10 +102,11 @@ jobs:
|
||||
run: |
|
||||
# Decrease the jobs sharply if we see repeated failures to try to
|
||||
# work around transient network errors even if it makes things
|
||||
# slower.
|
||||
# slower. Note that we allow passing targets that are incompatible and
|
||||
# skip thim as-if we were using `//...` style wild card patterns.
|
||||
./scripts/run_bazel.py \
|
||||
--attempts=5 --jobs-on-last-attempt=4 \
|
||||
test -c ${{ matrix.build_mode }} \
|
||||
test ${{ matrix.config.flags }} \
|
||||
--target_pattern_file=$TARGETS_FILE
|
||||
|
||||
# See "Disk space before build" in `test-setup`.
|
||||
|
||||
@@ -33,6 +33,11 @@ repos:
|
||||
rev: efaea7c61c774c0b1a9805fd999e754a2d19dbd1 # frozen: v1.2.5
|
||||
hooks:
|
||||
- id: check-google-doc-style
|
||||
exclude: |
|
||||
(?x)^(
|
||||
.*\.agents/.*|
|
||||
.*AGENTS.md
|
||||
)$
|
||||
- id: markdown-toc
|
||||
- repo: local
|
||||
hooks:
|
||||
|
||||
@@ -14,6 +14,7 @@ assistant, **read this first** to avoid common pitfalls.
|
||||
|
||||
- [General instructions](#general-instructions)
|
||||
- [Project structure](#project-structure)
|
||||
- [Bazel usage](#bazel-usage)
|
||||
- [Toolchain development](#toolchain-development)
|
||||
|
||||
## General instructions
|
||||
@@ -40,6 +41,12 @@ assistant, **read this first** to avoid common pitfalls.
|
||||
See the "Tool usage" skill for instructions on what tools to use in the
|
||||
carbon-lang project.
|
||||
|
||||
## Bazel usage
|
||||
|
||||
> [!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.
|
||||
|
||||
## Code style
|
||||
|
||||
See the "Code style" skill for instructions on formatting, style guides, and
|
||||
@@ -2,6 +2,8 @@
|
||||
# Exceptions. See /LICENSE for license information.
|
||||
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
|
||||
load("@bazel_skylib//rules:common_settings.bzl", "bool_flag", "bool_setting", "int_flag")
|
||||
|
||||
filegroup(
|
||||
name = "clang_tidy_config",
|
||||
srcs = [".clang-tidy"],
|
||||
@@ -13,3 +15,38 @@ alias(
|
||||
name = "generate_compile_commands",
|
||||
actual = "@wolfd_bazel_compile_commands//:generate_compile_commands",
|
||||
)
|
||||
|
||||
bool_setting(
|
||||
name = "runtimes_build",
|
||||
build_setting_default = False,
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
int_flag(
|
||||
name = "bootstrap_stage",
|
||||
build_setting_default = 0,
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
# A setting that causes bootstrapping to occur using the `exec` config rather
|
||||
# than the target config.
|
||||
#
|
||||
# The exec config is the more technically correct way of doing bootstrapping
|
||||
# than the target config. For example it allows bootstrapping with a target that
|
||||
# isn't compatible with the current execution host. However, in development
|
||||
# builds, it is likely to force building the entire toolchain twice -- once in
|
||||
# the target config for running test, and a second time in the exec config for
|
||||
# the bootstrap. As a consequence, this is disabled by default.
|
||||
#
|
||||
# TODO: Add documentation for using the bootstrap flags once stabilized.
|
||||
bool_flag(
|
||||
name = "bootstrap_exec_config",
|
||||
build_setting_default = False,
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
config_setting(
|
||||
name = "bootstrap_with_exec_config",
|
||||
flag_values = {"//:bootstrap_exec_config": "True"},
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
+10
-2
@@ -33,6 +33,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
- [Style](#style)
|
||||
- [Google Docs and Markdown](#google-docs-and-markdown)
|
||||
- [Other files](#other-files)
|
||||
- [Testing](#testing)
|
||||
- [License](#license)
|
||||
- [Google Docs](#google-docs)
|
||||
- [Markdown](#markdown)
|
||||
@@ -232,7 +233,7 @@ as well as helpful tooling that will ease the contribution process. For example,
|
||||
#### Using AI-based contribution tools
|
||||
|
||||
If you are using an AI assistant to help you contribute, or if you are an AI
|
||||
assistant yourself, please consult [GEMINI.md](/GEMINI.md) for high-density
|
||||
assistant yourself, please consult [AGENTS.md](/AGENTS.md) for high-density
|
||||
technical context and tips.
|
||||
|
||||
All submissions to Carbon need to follow our
|
||||
@@ -308,7 +309,6 @@ follow the Carbon documentation and coding styles.
|
||||
request.
|
||||
|
||||
- For code:
|
||||
|
||||
- New features should have a documented design that has been approved
|
||||
through the [evolution process](docs/project/evolution.md). This
|
||||
includes modifications to preexisting designs.
|
||||
@@ -427,6 +427,14 @@ Other style points to be aware of are:
|
||||
|
||||
If you're not sure what style to use, please ask on Discord or GitHub.
|
||||
|
||||
## Testing
|
||||
|
||||
Most development in the Carbon project is on the [toolchain](toolchain/). The
|
||||
toolchain contains some unit tests, but the majority of testing is done through
|
||||
`file_test` tests, which test the output of the different phases of the
|
||||
toolchain. The toolchain docs include instructions for
|
||||
[building and running tests](toolchain/docs/adding_features.md#tests-and-debugging).
|
||||
|
||||
## License
|
||||
|
||||
A license is required at the top of all documents and files.
|
||||
|
||||
+11
-12
@@ -40,6 +40,14 @@ bazel_dep(name = "tcmalloc", version = "0.0.0-20250927-12f2552")
|
||||
bazel_dep(name = "tree-sitter-bazel", version = "0.26.5")
|
||||
|
||||
bazel_dep(name = "wolfd_bazel_compile_commands", version = "0.5.2", dev_dependency = True)
|
||||
git_override(
|
||||
module_name = "wolfd_bazel_compile_commands",
|
||||
# This is https://github.com/wolfd/bazel-compile-commands/pull/3 which is
|
||||
# needed to correctly select target configurations in our compile commands.
|
||||
commit = "7c673ac868cd237f262bb37a7819b1a279566a66",
|
||||
remote = "https://github.com/chandlerc/bazel-compile-commands.git",
|
||||
)
|
||||
|
||||
bazel_dep(name = "bazel_clang_tidy", dev_dependency = True)
|
||||
git_override(
|
||||
module_name = "bazel_clang_tidy",
|
||||
@@ -56,16 +64,7 @@ use_repo(bazel_cc_toolchain, "bazel_cc_toolchain")
|
||||
|
||||
register_toolchains("@bazel_cc_toolchain//:all")
|
||||
|
||||
# TODO: Trying out `wolfd_bazel_compile_commands`, figure out if there are
|
||||
# issues. Once people have had a chance to try and check for problems, remove
|
||||
# one or the other.
|
||||
bazel_dep(name = "hedron_compile_commands", dev_dependency = True)
|
||||
git_override(
|
||||
module_name = "hedron_compile_commands",
|
||||
# HEAD as of 2026-01-28.
|
||||
commit = "abb61a688167623088f8768cc9264798df6a9d10",
|
||||
remote = "https://github.com/hedronvision/bazel-compile-commands-extractor.git",
|
||||
)
|
||||
register_toolchains("//toolchain/install:all")
|
||||
|
||||
# Required for llvm-project.
|
||||
bazel_dep(name = "platforms", version = "1.0.0")
|
||||
@@ -84,8 +83,8 @@ git_override(
|
||||
build_file_content = "# empty",
|
||||
# We pin to specific upstream commits and try to track top-of-tree
|
||||
# reasonably closely rather than pinning to a specific release.
|
||||
# HEAD as of 2026-03-09.
|
||||
commit = "d8474abfc1e7c3856b85b088f1133ae2e90da3d0",
|
||||
# HEAD as of 2026-04-01.
|
||||
commit = "b71eacea7687f68c11299e3bda5654fbbaa1e20e",
|
||||
patch_cmds = ["echo \"module(name='llvm-raw')\" > MODULE.bazel"],
|
||||
patch_strip = 1,
|
||||
patches = [
|
||||
|
||||
Generated
+1
-53
@@ -287,8 +287,6 @@
|
||||
"https://bcr.bazel.build/modules/tree-sitter-bazel/0.26.5/source.json": "cd6d21ca1b159961230ac6c33e0c7f638d7c40daa6746b4097d896441fd89074",
|
||||
"https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43",
|
||||
"https://bcr.bazel.build/modules/upb/0.0.0-20230516-61a97ef/MODULE.bazel": "c0df5e35ad55e264160417fd0875932ee3c9dda63d9fccace35ac62f45e1b6f9",
|
||||
"https://bcr.bazel.build/modules/wolfd_bazel_compile_commands/0.5.2/MODULE.bazel": "74527ae5c60c4974e5b6c9fc2f39b78af47b44559b1913324c8185ff4f2f3900",
|
||||
"https://bcr.bazel.build/modules/wolfd_bazel_compile_commands/0.5.2/source.json": "bbf0b6d7d2a2e58d238ef580fbf3072018e1c9447c9e2295b6a2b32d87a74338",
|
||||
"https://bcr.bazel.build/modules/yq.bzl/0.1.1/MODULE.bazel": "9039681f9bcb8958ee2c87ffc74bdafba9f4369096a2b5634b88abc0eaefa072",
|
||||
"https://bcr.bazel.build/modules/yq.bzl/0.1.1/source.json": "2d2bad780a9f2b9195a4a370314d2c17ae95eaa745cefc2e12fbc49759b15aa3",
|
||||
"https://bcr.bazel.build/modules/zlib-ng/2.0.7/MODULE.bazel": "3ca640b745b55f287e95aa0477e6cd76dfa0a565725d5412b7d8dae4274436c8",
|
||||
@@ -305,7 +303,7 @@
|
||||
"moduleExtensions": {
|
||||
"//bazel/cc_toolchains:clang_configuration.bzl%clang_toolchain_extension": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "44/bjnoCpU6JrWf798MGhACH/QjSv0OkcCi/jF+EGg0=",
|
||||
"bzlTransitiveDigest": "H3RsK0MbgutDMSlPWTwZq4Vk1U5sjDtgJ5MXQxg7GLU=",
|
||||
"usagesDigest": "lTxkeAFhR0iBEa3dg5hWvtd2HFCr5zCJx/fl27A+IKA=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
@@ -362,56 +360,6 @@
|
||||
"recordedRepoMappingEntries": []
|
||||
}
|
||||
},
|
||||
"@@hedron_compile_commands+//:workspace_setup.bzl%hedron_compile_commands_extension": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "GTNjt7oW5GiUBfcBtXAq5etqAZ6ue6PRd5nPaxeMoYM=",
|
||||
"usagesDigest": "CbJ2MjubH36j9xaONhhASfhodhpi5fzvuyg/IW2f7Ds=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
"generatedRepoSpecs": {},
|
||||
"recordedRepoMappingEntries": [
|
||||
[
|
||||
"hedron_compile_commands+",
|
||||
"bazel_tools",
|
||||
"bazel_tools"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"@@hedron_compile_commands+//:workspace_setup_transitive.bzl%hedron_compile_commands_extension": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "IfDf0vEa2jjQ11RNpUM0u4xftPXIs+pyM8IMVkRqVMk=",
|
||||
"usagesDigest": "yxZQbFglJyjpn7JZ9mhIc3EhLzZivlbs6wiHWOKJ/UA=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
"generatedRepoSpecs": {},
|
||||
"recordedRepoMappingEntries": []
|
||||
}
|
||||
},
|
||||
"@@hedron_compile_commands+//:workspace_setup_transitive_transitive.bzl%hedron_compile_commands_extension": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "1p58k3o2Jgjt/pBE7cb8WmmkplrSguIKma/h32x7X10=",
|
||||
"usagesDigest": "GkOuy/k8wz0dbKMeEJFKEJB3CWkMZt3DYcPgj4lALkI=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
"generatedRepoSpecs": {},
|
||||
"recordedRepoMappingEntries": []
|
||||
}
|
||||
},
|
||||
"@@hedron_compile_commands+//:workspace_setup_transitive_transitive_transitive.bzl%hedron_compile_commands_extension": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "arNWX4EleUjJxqkM5nCRTj+ce05Zz1gSdGH1DCKOoLs=",
|
||||
"usagesDigest": "WZExKK/BI4lqpUZfPpv4YARDE1Y7igQB+wYGKvNoCKs=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
"generatedRepoSpecs": {},
|
||||
"recordedRepoMappingEntries": []
|
||||
}
|
||||
},
|
||||
"@@protobuf+//python/dist:system_python.bzl%system_python_extension": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "pmsA+awieucfllLc2n7k8xEoPp0i5LF9Hw6mGX0cqSQ=",
|
||||
|
||||
@@ -27,7 +27,6 @@ If you haven't received a response, a couple steps to take are (in order):
|
||||
1. Contact individuals directly:
|
||||
- [Chandler Carruth](mailto:chandlerc@gmail.com)
|
||||
- [Richard Smith](mailto:richard@metafoo.co.uk)
|
||||
- [Jon Ross-Perkins](mailto:jperkins@google.com)
|
||||
2. Reach out on
|
||||
[#infra](https://discord.com/channels/655572317891461132/707150492370862090)
|
||||
on Discord ([invite](https://discord.gg/ZjVdShJDAs))
|
||||
|
||||
+44
-22
@@ -4,20 +4,10 @@
|
||||
|
||||
"""Provides rules for building Carbon files using the toolchain."""
|
||||
|
||||
load("@rules_cc//cc:action_names.bzl", "ACTION_NAMES")
|
||||
load("@rules_cc//cc/common:cc_common.bzl", "cc_common")
|
||||
load("@rules_cc//cc/common:cc_info.bzl", "CcInfo")
|
||||
|
||||
def _runtimes_path(runtimes_target):
|
||||
path = None
|
||||
for f in runtimes_target:
|
||||
if f.short_path.endswith("clang_resource_dir/lib"):
|
||||
path = f.path
|
||||
break
|
||||
|
||||
if not path:
|
||||
fail("Could not find the `clang_resource_dir` in target {}".format(runtimes_target.label))
|
||||
|
||||
return path[:-len("/clang_resource_dir/lib")]
|
||||
|
||||
def _carbon_binary_impl(ctx):
|
||||
toolchain_driver = ctx.executable.internal_exec_toolchain_driver
|
||||
toolchain_data = ctx.files.internal_exec_toolchain_data
|
||||
@@ -30,10 +20,12 @@ def _carbon_binary_impl(ctx):
|
||||
toolchain_data = ctx.files.internal_target_toolchain_data
|
||||
prebuilt_runtimes = ctx.files.internal_target_prebuilt_runtimes
|
||||
|
||||
# The extra link flags needed.
|
||||
link_flags = []
|
||||
|
||||
# Pass any C++ flags from our dependencies onto Carbon.
|
||||
dep_flags = []
|
||||
dep_hdrs = []
|
||||
dep_link_flags = []
|
||||
dep_link_inputs = []
|
||||
for dep in ctx.attr.deps:
|
||||
if CcInfo in dep:
|
||||
@@ -47,15 +39,16 @@ def _carbon_binary_impl(ctx):
|
||||
dep_flags += ["--clang-arg=-isystem{0}".format(path) for path in cc_info.compilation_context.system_includes.to_list()]
|
||||
dep_hdrs.append(cc_info.compilation_context.headers)
|
||||
for link_input in cc_info.linking_context.linker_inputs.to_list():
|
||||
# TODO: `carbon link` doesn't support linker flags yet.
|
||||
# dep_link_flags += link_input.user_link_flags
|
||||
link_flags += link_input.user_link_flags
|
||||
dep_link_inputs += link_input.additional_inputs
|
||||
for lib in link_input.libraries:
|
||||
dep_link_inputs += [dep for dep in [lib.dynamic_library, lib.static_library] if dep]
|
||||
dep_link_inputs += lib.objects
|
||||
if DefaultInfo in dep:
|
||||
dep_link_inputs += dep[DefaultInfo].files.to_list()
|
||||
dep_link_flags += [dep.path for dep in dep_link_inputs]
|
||||
|
||||
# Add the dependencies' link flags and inputs to the link flags.
|
||||
link_flags += [dep.path for dep in dep_link_inputs]
|
||||
|
||||
# Build object files for the prelude and for the binary itself.
|
||||
# TODO: Eventually the prelude should be build as a separate `carbon_library`.
|
||||
@@ -95,13 +88,41 @@ def _carbon_binary_impl(ctx):
|
||||
progress_message = "Compiling " + src.short_path,
|
||||
)
|
||||
|
||||
# Add the Carbon object files to the link flags.
|
||||
link_flags += [o.path for o in objs]
|
||||
|
||||
bin = ctx.actions.declare_file(ctx.label.name)
|
||||
|
||||
# Get all link options from the toolchain and dependencies using standard pattern.
|
||||
cc_toolchain = ctx.attr._cc_toolchain[cc_common.CcToolchainInfo]
|
||||
feature_configuration = cc_common.configure_features(
|
||||
ctx = ctx,
|
||||
cc_toolchain = cc_toolchain,
|
||||
requested_features = ctx.features,
|
||||
unsupported_features = ctx.disabled_features,
|
||||
)
|
||||
variables = cc_common.create_link_variables(
|
||||
feature_configuration = feature_configuration,
|
||||
cc_toolchain = cc_toolchain,
|
||||
is_using_linker = True,
|
||||
user_link_flags = link_flags + [
|
||||
# TODO: Remove once the sanitizer runtimes are available.
|
||||
"-fno-sanitize=all",
|
||||
],
|
||||
output_file = bin.path,
|
||||
)
|
||||
full_link_flags = cc_common.get_memory_inefficient_command_line(
|
||||
feature_configuration = feature_configuration,
|
||||
action_name = ACTION_NAMES.cpp_link_executable,
|
||||
variables = variables,
|
||||
)
|
||||
|
||||
ctx.actions.run(
|
||||
outputs = [bin],
|
||||
inputs = objs + dep_link_inputs,
|
||||
executable = toolchain_driver,
|
||||
tools = depset(toolchain_data + prebuilt_runtimes),
|
||||
arguments = ["--prebuilt-runtimes=" + _runtimes_path(prebuilt_runtimes), "link", "--output=" + bin.path] + ["--"] + dep_link_flags + [o.path for o in objs],
|
||||
arguments = full_link_flags,
|
||||
mnemonic = "CarbonLink",
|
||||
progress_message = "Linking " + bin.short_path,
|
||||
)
|
||||
@@ -148,9 +169,10 @@ _carbon_binary_internal = rule(
|
||||
),
|
||||
"prelude_srcs": attr.label_list(allow_files = [".carbon"]),
|
||||
"srcs": attr.label_list(allow_files = [".carbon"]),
|
||||
"_cc_toolchain": attr.label(default = "@bazel_tools//tools/cpp:current_cc_toolchain"),
|
||||
"_cc_toolchain": attr.label(default = "//toolchain/install:carbon_stage1_cc_toolchain"),
|
||||
},
|
||||
executable = True,
|
||||
fragments = ["cpp"],
|
||||
)
|
||||
|
||||
def carbon_binary(name, srcs, deps = [], flags = [], tags = []):
|
||||
@@ -177,7 +199,7 @@ def carbon_binary(name, srcs, deps = [], flags = [], tags = []):
|
||||
# `select` which one we use.
|
||||
internal_exec_toolchain_driver = select({
|
||||
"//bazel/carbon_rules:use_target_config_carbon_rules_config": None,
|
||||
"//conditions:default": "//toolchain/install:prefix/bin/carbon",
|
||||
"//conditions:default": "//toolchain/install:carbon-busybox",
|
||||
}),
|
||||
internal_exec_toolchain_data = select({
|
||||
"//bazel/carbon_rules:use_target_config_carbon_rules_config": None,
|
||||
@@ -185,10 +207,10 @@ def carbon_binary(name, srcs, deps = [], flags = [], tags = []):
|
||||
}),
|
||||
internal_exec_prebuilt_runtimes = select({
|
||||
"//bazel/carbon_rules:use_target_config_carbon_rules_config": None,
|
||||
"//conditions:default": "//toolchain/driver:prebuilt_runtimes",
|
||||
"//conditions:default": "//toolchain/install:built_runtimes",
|
||||
}),
|
||||
internal_target_toolchain_driver = select({
|
||||
"//bazel/carbon_rules:use_target_config_carbon_rules_config": "//toolchain/install:prefix/bin/carbon",
|
||||
"//bazel/carbon_rules:use_target_config_carbon_rules_config": "//toolchain/install:carbon-busybox",
|
||||
"//conditions:default": None,
|
||||
}),
|
||||
internal_target_toolchain_data = select({
|
||||
@@ -196,7 +218,7 @@ def carbon_binary(name, srcs, deps = [], flags = [], tags = []):
|
||||
"//conditions:default": None,
|
||||
}),
|
||||
internal_target_prebuilt_runtimes = select({
|
||||
"//bazel/carbon_rules:use_target_config_carbon_rules_config": "//toolchain/driver:prebuilt_runtimes",
|
||||
"//bazel/carbon_rules:use_target_config_carbon_rules_config": "//toolchain/install:built_runtimes",
|
||||
"//conditions:default": None,
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -3,9 +3,13 @@
|
||||
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
|
||||
load("@bazel_skylib//lib:selects.bzl", "selects")
|
||||
load("@rules_python//python:defs.bzl", "py_library", "py_test")
|
||||
load(":carbon_cc_toolchain_config.bzl", "gen_cc_toolchain_paths_with_stage")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
exports_files(["carbon_cc_toolchain_config.bzl"])
|
||||
|
||||
# For use by defs.bzl.
|
||||
# Matches when asan is enabled on a macOS platform.
|
||||
selects.config_setting_group(
|
||||
@@ -61,5 +65,31 @@ filegroup(
|
||||
"cc_toolchain_optimization.bzl",
|
||||
"cc_toolchain_sanitizer_features.bzl",
|
||||
"cc_toolchain_tools.bzl",
|
||||
|
||||
# TODO: Remove this once we can remove the use of it from Carbon
|
||||
# toolchain rules.
|
||||
"cc_toolchain_carbon_project_features.bzl",
|
||||
],
|
||||
)
|
||||
|
||||
gen_cc_toolchain_paths_with_stage(
|
||||
name = "gen_cc_tools_paths",
|
||||
stage = 0,
|
||||
)
|
||||
|
||||
# Test that the default toolchain's Make variables expand correctly.
|
||||
py_test(
|
||||
name = "cc_tools_test",
|
||||
srcs = ["cc_tools_test.py"],
|
||||
args = ["$(location :gen_cc_tools_paths)"],
|
||||
data = [":gen_cc_tools_paths"],
|
||||
deps = [":cc_tools_test_lib"],
|
||||
)
|
||||
|
||||
# Library containing the test logic, used by tests in other packages.
|
||||
py_library(
|
||||
name = "cc_tools_test_lib",
|
||||
srcs = ["cc_tools_test.py"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = ["@bazel_tools//tools/python/runfiles"],
|
||||
)
|
||||
|
||||
@@ -0,0 +1,547 @@
|
||||
# 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
|
||||
|
||||
"""Starlark cc_toolchain configuration rules for using the Carbon toolchain"""
|
||||
|
||||
load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain")
|
||||
load("@rules_cc//cc:action_names.bzl", "ACTION_NAMES")
|
||||
load(
|
||||
"@rules_cc//cc:cc_toolchain_config_lib.bzl",
|
||||
"action_config",
|
||||
"flag_group",
|
||||
"flag_set",
|
||||
"tool",
|
||||
)
|
||||
load(
|
||||
"@rules_cc//cc:defs.bzl",
|
||||
"CcToolchainConfigInfo",
|
||||
"cc_toolchain",
|
||||
)
|
||||
load("@rules_cc//cc/common:cc_common.bzl", "cc_common")
|
||||
load("//toolchain/runtimes:carbon_runtimes.bzl", "carbon_runtimes_build")
|
||||
load(
|
||||
"carbon_clang_variables.bzl",
|
||||
"clang_include_dirs",
|
||||
"clang_resource_dir",
|
||||
"clang_sysroot",
|
||||
)
|
||||
load(
|
||||
"cc_toolchain_actions.bzl",
|
||||
"all_c_compile_actions",
|
||||
"all_cpp_compile_actions",
|
||||
"all_link_actions",
|
||||
)
|
||||
load("cc_toolchain_carbon_project_features.bzl", "carbon_project_features")
|
||||
load("cc_toolchain_features.bzl", "clang_cc_toolchain_features")
|
||||
load(
|
||||
":cc_toolchain_tools.bzl",
|
||||
"llvm_tool_paths",
|
||||
)
|
||||
|
||||
def _make_action_configs(tools, runtimes_path = None):
|
||||
runtimes_flag = "--no-build-runtimes"
|
||||
if runtimes_path:
|
||||
runtimes_flag = "--prebuilt-runtimes={0}".format(runtimes_path)
|
||||
|
||||
return [
|
||||
action_config(
|
||||
action_name = name,
|
||||
enabled = True,
|
||||
tools = [tools.clang],
|
||||
)
|
||||
for name in all_c_compile_actions
|
||||
] + [
|
||||
action_config(
|
||||
action_name = name,
|
||||
enabled = True,
|
||||
tools = [tools.clangpp],
|
||||
)
|
||||
for name in all_cpp_compile_actions
|
||||
] + [
|
||||
action_config(
|
||||
action_name = name,
|
||||
enabled = True,
|
||||
tools = [tools.carbon_busybox],
|
||||
flag_sets = [flag_set(flag_groups = [flag_group(flags = [
|
||||
runtimes_flag,
|
||||
"link",
|
||||
# We want to allow Bazel to intermingle linked object files and
|
||||
# Clang-spelled link flags. The first `--` starts the list of
|
||||
# initial object files by ending flags to the `link` subcommand,
|
||||
# and the second `--` switches to Clang-spelled flags.
|
||||
"--",
|
||||
"--",
|
||||
])])],
|
||||
)
|
||||
for name in all_link_actions
|
||||
] + [
|
||||
action_config(
|
||||
action_name = name,
|
||||
enabled = True,
|
||||
tools = [tools.llvm_ar],
|
||||
)
|
||||
for name in [ACTION_NAMES.cpp_link_static_library]
|
||||
] + [
|
||||
action_config(
|
||||
action_name = name,
|
||||
enabled = True,
|
||||
tools = [tools.llvm_strip],
|
||||
)
|
||||
for name in [ACTION_NAMES.strip]
|
||||
]
|
||||
|
||||
def _compute_clang_system_include_dirs():
|
||||
system_include_dirs_start_index = None
|
||||
for index, dir in enumerate(clang_include_dirs):
|
||||
# Skip over the include search directories until we find the resource
|
||||
# directory. The system include directories are everything after that.
|
||||
if dir.startswith(clang_resource_dir):
|
||||
system_include_dirs_start_index = index + 1
|
||||
break
|
||||
if not system_include_dirs_start_index:
|
||||
fail("Could not find the resource directory in the clang include " +
|
||||
"directories: {}".format(clang_include_dirs))
|
||||
return clang_include_dirs[system_include_dirs_start_index:]
|
||||
|
||||
def _carbon_cc_toolchain_config_impl(ctx):
|
||||
llvm_bindir = "llvm/bin"
|
||||
clang_bindir = llvm_bindir
|
||||
tools = struct(
|
||||
carbon_busybox = tool(path = "carbon-busybox"),
|
||||
clang = tool(path = clang_bindir + "/clang"),
|
||||
clangpp = tool(path = clang_bindir + "/clang++"),
|
||||
llvm_ar = tool(path = llvm_bindir + "/llvm-ar"),
|
||||
llvm_strip = tool(path = llvm_bindir + "/llvm-strip"),
|
||||
)
|
||||
if ctx.attr.bins:
|
||||
carbon_busybox = None
|
||||
clang = None
|
||||
clangpp = None
|
||||
llvm_ar = None
|
||||
llvm_strip = None
|
||||
for f in ctx.files.bins:
|
||||
if f.basename == "carbon-busybox":
|
||||
carbon_busybox = f
|
||||
elif f.basename == "clang":
|
||||
clang = f
|
||||
elif f.basename == "clang++":
|
||||
clangpp = f
|
||||
elif f.basename == "llvm-ar":
|
||||
llvm_ar = f
|
||||
elif f.basename == "llvm-strip":
|
||||
llvm_strip = f
|
||||
if not all([carbon_busybox, clang, clangpp, llvm_ar, llvm_strip]):
|
||||
fail("Missing required tool in bins: {0}".format(ctx.attr.bins))
|
||||
llvm_bindir = llvm_ar.dirname
|
||||
clang_bindir = clang.dirname
|
||||
tools = struct(
|
||||
carbon_busybox = tool(tool = carbon_busybox),
|
||||
clang = tool(tool = clang),
|
||||
clangpp = tool(tool = clangpp),
|
||||
llvm_ar = tool(tool = llvm_ar),
|
||||
llvm_strip = tool(tool = llvm_strip),
|
||||
)
|
||||
|
||||
# Only use a sysroot if a non-trivial one is set in Carbon's config.
|
||||
builtin_sysroot = None
|
||||
sysroot_include_search = []
|
||||
if clang_sysroot != "None" and clang_sysroot != "/":
|
||||
builtin_sysroot = clang_sysroot
|
||||
sysroot_include_search = ["%sysroot%/usr/include"]
|
||||
|
||||
runtimes_path = None
|
||||
if ctx.attr.runtimes:
|
||||
for f in ctx.files.runtimes:
|
||||
if f.basename == "runtimes_root":
|
||||
runtimes_path = f.dirname
|
||||
break
|
||||
if not runtimes_path:
|
||||
fail("Unable to compute the runtimes path for: {0}".format(
|
||||
ctx.attr.runtimes,
|
||||
))
|
||||
|
||||
identifier = "{0}_toolchain_{1}_{2}".format(
|
||||
ctx.attr.identifier_prefix,
|
||||
ctx.attr.target_cpu,
|
||||
ctx.attr.target_os,
|
||||
)
|
||||
return cc_common.create_cc_toolchain_config_info(
|
||||
ctx = ctx,
|
||||
features = clang_cc_toolchain_features(
|
||||
target_os = ctx.attr.target_os,
|
||||
target_cpu = ctx.attr.target_cpu,
|
||||
|
||||
# TODO: This should be configured externally rather than here so
|
||||
# that the install Carbon toolchain doesn't automatically include
|
||||
# Carbon-project-specific flags. However, that is especially awkward
|
||||
# to do until we fully migrate to a rules-based toolchain, and the
|
||||
# project-specific flags are largely harmless at the moment. We also
|
||||
# omit a meaningful cache key as when using the Carbon toolchain we
|
||||
# don't need it as it is a hermetic part of Bazel.
|
||||
project_features = carbon_project_features(cache_key = ""),
|
||||
),
|
||||
action_configs = _make_action_configs(tools, runtimes_path),
|
||||
cxx_builtin_include_directories = [
|
||||
"runtimes/libunwind/include",
|
||||
"runtimes/libcxx/include",
|
||||
"runtimes/libcxxabi/include",
|
||||
"{}/include".format(clang_resource_dir),
|
||||
"runtimes/clang_resource_dir/include",
|
||||
] + _compute_clang_system_include_dirs() + sysroot_include_search,
|
||||
builtin_sysroot = builtin_sysroot,
|
||||
|
||||
# This configuration only supports local non-cross builds so derive
|
||||
# everything from the target CPU selected.
|
||||
toolchain_identifier = identifier,
|
||||
|
||||
# This is used to expose a "flag" that `config_setting` rules can use to
|
||||
# determine if the compiler is Clang.
|
||||
compiler = "clang",
|
||||
|
||||
# Pass in our tool paths to expose Make variables like $(NM) and
|
||||
# $(OBJCOPY).
|
||||
tool_paths = llvm_tool_paths(llvm_bindir, clang_bindir),
|
||||
)
|
||||
|
||||
carbon_cc_toolchain_config = rule(
|
||||
implementation = _carbon_cc_toolchain_config_impl,
|
||||
attrs = {
|
||||
"bins": attr.label(mandatory = False),
|
||||
"identifier_prefix": attr.string(mandatory = True),
|
||||
"runtimes": attr.label(mandatory = False),
|
||||
"target_cpu": attr.string(mandatory = True),
|
||||
"target_os": attr.string(mandatory = True),
|
||||
},
|
||||
provides = [CcToolchainConfigInfo],
|
||||
)
|
||||
|
||||
def _transition_with_stage_impl(_, attr):
|
||||
return {
|
||||
"//:bootstrap_stage": attr.stage,
|
||||
"//:runtimes_build": attr.enable_runtimes_build,
|
||||
}
|
||||
|
||||
_transition_with_stage = transition(
|
||||
inputs = [],
|
||||
outputs = [
|
||||
"//:bootstrap_stage",
|
||||
"//:runtimes_build",
|
||||
],
|
||||
implementation = _transition_with_stage_impl,
|
||||
)
|
||||
|
||||
def _filegroup_with_stage_impl(ctx):
|
||||
return [DefaultInfo(files = depset(ctx.files.srcs))]
|
||||
|
||||
filegroup_with_stage = rule(
|
||||
implementation = _filegroup_with_stage_impl,
|
||||
attrs = {
|
||||
# Whether to enable runtimes building for the sources of this filegroup.
|
||||
"enable_runtimes_build": attr.bool(default = False),
|
||||
|
||||
# Mark that our dependencies are built through a transition.
|
||||
"srcs": attr.label_list(mandatory = True, cfg = _transition_with_stage),
|
||||
|
||||
# The bootstrap stage that the sources of this filegroup should be built
|
||||
# with.
|
||||
"stage": attr.int(mandatory = True),
|
||||
|
||||
# Enable transitions in this rule.
|
||||
"_allowlist_function_transition": attr.label(
|
||||
default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
|
||||
),
|
||||
},
|
||||
doc = """
|
||||
A filegroup whose sources are built using a specific toolchain stage, and
|
||||
which provides an interface to build those sources with or without enabling
|
||||
runtimes building.
|
||||
""",
|
||||
)
|
||||
|
||||
def _exec_filegroup_impl(ctx):
|
||||
return [DefaultInfo(files = depset(ctx.files.srcs))]
|
||||
|
||||
_exec_filegroup = rule(
|
||||
implementation = _exec_filegroup_impl,
|
||||
attrs = {
|
||||
"srcs": attr.label_list(cfg = "exec"),
|
||||
},
|
||||
)
|
||||
|
||||
def filegroup_with_stage_and_exec(name, srcs, stage, tags = []):
|
||||
"""Wraps `filegroup_with_stage` with a conditional `exec` config transition.
|
||||
|
||||
When `//:bootstrap_exec_config` is disabled, this works exactly like
|
||||
`filegroup_with_stage`. But when it is _enabled_, it also adds an `exec`
|
||||
config transition. This allows bootstrapping for a target that is not exec
|
||||
compatible with the host, and in general makes bootstrapping more robust at
|
||||
the expense of a likely duplicate build of the entire toolchain.
|
||||
"""
|
||||
filegroup_with_stage(
|
||||
name = name + "_stage_only",
|
||||
srcs = srcs,
|
||||
stage = stage,
|
||||
tags = tags,
|
||||
)
|
||||
|
||||
_exec_filegroup(
|
||||
name = name + "_with_exec",
|
||||
srcs = [":" + name + "_stage_only"],
|
||||
tags = tags,
|
||||
)
|
||||
|
||||
native.alias(
|
||||
name = name,
|
||||
actual = select({
|
||||
"//:bootstrap_with_exec_config": ":" + name + "_with_exec",
|
||||
"//conditions:default": ":" + name + "_stage_only",
|
||||
}),
|
||||
tags = tags,
|
||||
)
|
||||
|
||||
def _gen_cc_toolchain_paths_impl(ctx):
|
||||
cc_toolchain = find_cpp_toolchain(ctx)
|
||||
|
||||
expanded_vars = [
|
||||
ctx.expand_make_variables("vars", v, {})
|
||||
for v in ctx.attr.vars
|
||||
]
|
||||
|
||||
out = ctx.actions.declare_file(ctx.attr.name + ".txt")
|
||||
ctx.actions.write(out, "\n".join(expanded_vars) + "\n")
|
||||
|
||||
# Include all toolchain files in runfiles.
|
||||
runfiles = ctx.runfiles(files = [out]).merge(
|
||||
ctx.runfiles(transitive_files = cc_toolchain.all_files),
|
||||
)
|
||||
|
||||
return [DefaultInfo(files = depset([out]), runfiles = runfiles)]
|
||||
|
||||
gen_cc_toolchain_paths_with_stage = rule(
|
||||
implementation = _gen_cc_toolchain_paths_impl,
|
||||
attrs = {
|
||||
"enable_runtimes_build": attr.bool(default = False),
|
||||
"stage": attr.int(mandatory = True),
|
||||
"vars": attr.string_list(
|
||||
default = ["$(CC)", "$(AR)", "$(NM)", "$(OBJCOPY)", "$(STRIP)"],
|
||||
),
|
||||
"_allowlist_function_transition": attr.label(
|
||||
default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
|
||||
),
|
||||
"_cc_toolchain": attr.label(
|
||||
default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
|
||||
),
|
||||
},
|
||||
toolchains = ["@bazel_tools//tools/cpp:toolchain_type"],
|
||||
cfg = _transition_with_stage,
|
||||
)
|
||||
|
||||
def carbon_cc_toolchain_suite(
|
||||
name,
|
||||
all_hdrs,
|
||||
base_files,
|
||||
clang_hdrs,
|
||||
platforms,
|
||||
runtimes_cfg,
|
||||
build_stage = 1,
|
||||
base_stage = 0,
|
||||
tags = []):
|
||||
"""Create a Carbon `cc_toolchain` for the current target platform.
|
||||
|
||||
This provides the final toolchain for Carbon, but also all of the
|
||||
infrastructure for supporting on-demand built runtimes in this toolchain.
|
||||
|
||||
There is also support for bootstrapping, where one `build_stage` toolchain
|
||||
builds on top of another `base_stage`.
|
||||
|
||||
Args:
|
||||
name:
|
||||
The name of the toolchain suite to produce, used as the base of the
|
||||
names of each component of the toolchain suite.
|
||||
all_hdrs: A list of header files to include in the toolchain.
|
||||
base_files: A list of files to include in the toolchain.
|
||||
build_stage: The stage to use for the build files.
|
||||
base_stage: The stage to use for the base files.
|
||||
clang_hdrs: A list of header files to include in the toolchain.
|
||||
platforms: An array of (os, cpu) pairs to support in the toolchain.
|
||||
runtimes_cfg: The runtimes configuration to use in the toolchain.
|
||||
tags: Tags to apply to the toolchain.
|
||||
"""
|
||||
|
||||
# First, declare file groups that are explicitly built using the base stage,
|
||||
# and not in the runtimes build. These allow us to form the inputs to both
|
||||
# the runtimes toolchain and the main toolchain of this stage that are built
|
||||
# entirely by the base stage toolchain.
|
||||
filegroup_with_stage_and_exec(
|
||||
name = "{}_clang_hdrs".format(name),
|
||||
srcs = clang_hdrs,
|
||||
stage = base_stage,
|
||||
tags = tags,
|
||||
)
|
||||
|
||||
filegroup_with_stage_and_exec(
|
||||
name = "{}_base_files".format(name),
|
||||
srcs = base_files,
|
||||
stage = base_stage,
|
||||
tags = tags,
|
||||
)
|
||||
|
||||
filegroup_with_stage_and_exec(
|
||||
name = "{}_runtimes_compile_files".format(name),
|
||||
srcs = [
|
||||
":{}_base_files".format(name),
|
||||
":{}_clang_hdrs".format(name),
|
||||
],
|
||||
stage = base_stage,
|
||||
tags = tags,
|
||||
)
|
||||
|
||||
filegroup_with_stage_and_exec(
|
||||
name = "{}_compile_files".format(name),
|
||||
srcs = [":{}_base_files".format(name)] + all_hdrs,
|
||||
stage = base_stage,
|
||||
tags = tags,
|
||||
)
|
||||
|
||||
# Now build a configuration and toolchain that is configured to work
|
||||
# _without_ runtimes, and be used to _build_ the runtimes on-demand.
|
||||
carbon_cc_toolchain_config(
|
||||
name = "{}_runtimes_toolchain_config".format(name),
|
||||
identifier_prefix = "{}_runtimes".format(name),
|
||||
target_cpu = select({
|
||||
# Note that we need to select on both OS and CPU so that we end up
|
||||
# spelling the CPU in the correct OS-specific ways.
|
||||
":is_{}_{}".format(os, cpu): cpu
|
||||
for os, cpus in platforms.items()
|
||||
for cpu in cpus
|
||||
}),
|
||||
target_os = select({
|
||||
"@platforms//os:{}".format(os): os
|
||||
for os in platforms.keys()
|
||||
}),
|
||||
bins = ":{}_base_files".format(name),
|
||||
tags = tags,
|
||||
)
|
||||
|
||||
cc_toolchain(
|
||||
name = "{}_runtimes_cc_toolchain".format(name),
|
||||
all_files = ":{}_runtimes_compile_files".format(name),
|
||||
ar_files = ":{}_base_files".format(name),
|
||||
as_files = ":{}_runtimes_compile_files".format(name),
|
||||
compiler_files = ":{}_runtimes_compile_files".format(name),
|
||||
dwp_files = ":{}_base_files".format(name),
|
||||
linker_files = ":{}_base_files".format(name),
|
||||
objcopy_files = ":{}_base_files".format(name),
|
||||
strip_files = ":{}_base_files".format(name),
|
||||
toolchain_config = ":{}_runtimes_toolchain_config".format(name),
|
||||
toolchain_identifier = select({
|
||||
":is_{}_{}".format(os, cpu): "{}_{}_{}_runtimes_toolchain".format(name, os, cpu)
|
||||
for os, cpus in platforms.items()
|
||||
for cpu in cpus
|
||||
}),
|
||||
tags = tags,
|
||||
)
|
||||
|
||||
native.toolchain(
|
||||
name = "{}_runtimes_toolchain".format(name),
|
||||
target_settings = [
|
||||
":is_bootstrap_stage_{}".format(build_stage),
|
||||
":is_runtimes_build",
|
||||
],
|
||||
use_target_platform_constraints = True,
|
||||
toolchain = ":{}_runtimes_cc_toolchain".format(name),
|
||||
toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
|
||||
tags = tags,
|
||||
)
|
||||
|
||||
# Now that we have a toolchain for building runtimes, actually do the build
|
||||
# here using the runtimes config provided to us. This is important to do
|
||||
# here because we need each runtimes build for a particular bootstrapping
|
||||
# stage of the toolchain to be distinct.
|
||||
carbon_runtimes_build(
|
||||
name = "{}_runtimes_build".format(name),
|
||||
config = runtimes_cfg,
|
||||
clang_hdrs = [":{}_clang_hdrs".format(name)],
|
||||
tags = tags,
|
||||
)
|
||||
|
||||
# Wrap the built runtimes for this stage in a filegroup that ensures they
|
||||
# are built at this stage, but with the runtimes build enabled. This will
|
||||
# select the runtimes build toolchain above that doesn't yet provide any
|
||||
# runtimes, avoiding a cycle when the main toolchain below depends on these
|
||||
# runtimes.
|
||||
filegroup_with_stage(
|
||||
name = "{}_runtimes".format(name),
|
||||
enable_runtimes_build = True,
|
||||
srcs = ["{}_runtimes_build".format(name)],
|
||||
stage = build_stage,
|
||||
tags = tags,
|
||||
)
|
||||
|
||||
# Now we can build the main toolchain configuration, filegroups including
|
||||
# the on-demand built runtimes, and the final tolochain itself.
|
||||
carbon_cc_toolchain_config(
|
||||
name = "{}_toolchain_config".format(name),
|
||||
identifier_prefix = name,
|
||||
target_cpu = select({
|
||||
# Note that we need to select on both OS and CPU so that we end up
|
||||
# spelling the CPU in the correct OS-specific ways.
|
||||
":is_{}_{}".format(os, cpu): cpu
|
||||
for os, cpus in platforms.items()
|
||||
for cpu in cpus
|
||||
}),
|
||||
target_os = select({
|
||||
"@platforms//os:{}".format(os): os
|
||||
for os in platforms.keys()
|
||||
}),
|
||||
runtimes = ":{}_runtimes".format(name),
|
||||
bins = ":{}_base_files".format(name),
|
||||
tags = tags,
|
||||
)
|
||||
|
||||
native.filegroup(
|
||||
name = "{}_linker_files".format(name),
|
||||
srcs = [
|
||||
":{}_base_files".format(name),
|
||||
":{}_runtimes".format(name),
|
||||
],
|
||||
tags = tags,
|
||||
)
|
||||
|
||||
native.filegroup(
|
||||
name = "{}_all_files".format(name),
|
||||
srcs = [
|
||||
":{}_compile_files".format(name),
|
||||
":{}_linker_files".format(name),
|
||||
],
|
||||
tags = tags,
|
||||
)
|
||||
|
||||
cc_toolchain(
|
||||
name = "{}_cc_toolchain".format(name),
|
||||
all_files = ":{}_all_files".format(name),
|
||||
ar_files = ":" + name + "_base_files",
|
||||
as_files = ":" + name + "_compile_files",
|
||||
compiler_files = ":" + name + "_compile_files",
|
||||
dwp_files = ":" + name + "_linker_files",
|
||||
linker_files = ":" + name + "_linker_files",
|
||||
objcopy_files = ":" + name + "_base_files",
|
||||
strip_files = ":" + name + "_base_files",
|
||||
toolchain_config = ":" + name + "_toolchain_config",
|
||||
toolchain_identifier = select({
|
||||
":is_{}_{}".format(os, cpu): "{}_{}_{}_toolchain".format(name, os, cpu)
|
||||
for os, cpus in platforms.items()
|
||||
for cpu in cpus
|
||||
}),
|
||||
tags = tags,
|
||||
)
|
||||
|
||||
native.toolchain(
|
||||
name = name + "_toolchain",
|
||||
target_settings = [":is_bootstrap_stage_{}".format(build_stage), ":not_runtimes_build"],
|
||||
use_target_platform_constraints = True,
|
||||
toolchain = ":" + name + "_cc_toolchain",
|
||||
toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
|
||||
tags = tags,
|
||||
)
|
||||
@@ -0,0 +1,20 @@
|
||||
# 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
|
||||
|
||||
"""A Starlark file exporting detected Carbon toolchain configuration variables.
|
||||
|
||||
This file gets processed by a repository rule, substituting the `VARIABLE`s with
|
||||
values, for example using an invocation of `carbon config`.
|
||||
"""
|
||||
|
||||
load(
|
||||
"@bazel_cc_toolchain//:clang_detected_variables.bzl",
|
||||
_clang_include_dirs = "clang_include_dirs",
|
||||
_clang_resource_dir = "clang_resource_dir",
|
||||
_sysroot_dir = "sysroot_dir",
|
||||
)
|
||||
|
||||
clang_include_dirs = _clang_include_dirs
|
||||
clang_resource_dir = _clang_resource_dir
|
||||
clang_sysroot = _sysroot_dir
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
"""Definitions used for the base features of a `cc_toolchain_config`."""
|
||||
|
||||
load("@rules_cc//cc:action_names.bzl", "ACTION_NAMES")
|
||||
load(
|
||||
"@rules_cc//cc:cc_toolchain_config_lib.bzl",
|
||||
"feature",
|
||||
@@ -92,12 +93,41 @@ output_flags_feature = feature(
|
||||
],
|
||||
)
|
||||
|
||||
strip_feature = feature(
|
||||
name = "strip_flags",
|
||||
enabled = True,
|
||||
flag_sets = [flag_set(
|
||||
actions = [ACTION_NAMES.strip],
|
||||
flag_groups = [
|
||||
flag_group(
|
||||
flags = ["-S"],
|
||||
),
|
||||
flag_group(
|
||||
flags = ["-p"],
|
||||
),
|
||||
flag_group(
|
||||
expand_if_available = "output_file",
|
||||
flags = ["-o", "%{output_file}"],
|
||||
),
|
||||
flag_group(
|
||||
iterate_over = "stripopts",
|
||||
flags = ["%{stripopts}"],
|
||||
),
|
||||
flag_group(
|
||||
expand_if_available = "input_file",
|
||||
flags = ["%{input_file}"],
|
||||
),
|
||||
],
|
||||
)],
|
||||
)
|
||||
|
||||
base_features = [
|
||||
dbg_feature,
|
||||
fastbuild_feature,
|
||||
host_feature,
|
||||
no_legacy_features_feature,
|
||||
opt_feature,
|
||||
strip_feature,
|
||||
supports_pic_feature,
|
||||
supports_dynamic_linker_feature,
|
||||
supports_start_end_lib_feature,
|
||||
|
||||
@@ -27,10 +27,9 @@ carbon_project_fastbuild_feature = feature(
|
||||
enabled = True,
|
||||
requires = [feature_set(["fastbuild"])],
|
||||
implies = [
|
||||
"asan",
|
||||
"asan_min_size",
|
||||
"minimal_optimization_flags",
|
||||
"minimal_debug_info_flags",
|
||||
"preserve_call_stacks",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ minimal_optimization_flags = feature(
|
||||
name = "minimal_optimization_flags",
|
||||
flag_sets = [flag_set(
|
||||
actions = codegen_compile_actions,
|
||||
flag_groups = [flag_group(flags = ["-O1"])],
|
||||
flag_groups = [flag_group(flags = ["-Og"])],
|
||||
)],
|
||||
)
|
||||
default_optimization_flags = feature(
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
"""Tests that the C++ toolchain tools can be executed.
|
||||
|
||||
This script reads a file containing paths to C++ tools (like clang++, llvm-ar)
|
||||
and attempts to run each with `--version` to verify they are functional.
|
||||
"""
|
||||
|
||||
__copyright__ = """
|
||||
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
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from bazel_tools.tools.python.runfiles import runfiles
|
||||
|
||||
|
||||
def test_tools() -> None:
|
||||
"""Reads paths from file and runs each tool with --version."""
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: cc_tools_test.py <paths_file>")
|
||||
sys.exit(1)
|
||||
|
||||
paths_file = sys.argv[1]
|
||||
print(f"Reading tools from: {paths_file}")
|
||||
with open(paths_file, "r") as f:
|
||||
tools = [line.strip() for line in f if line.strip()]
|
||||
|
||||
print(f"Testing tools: {tools}")
|
||||
r = runfiles.Create()
|
||||
repo_name = os.environ.get("TEST_WORKSPACE") or "_main"
|
||||
|
||||
for tool in tools:
|
||||
if "bazel-out/" in tool:
|
||||
_, _, rest = tool.partition("bazel-out/")
|
||||
_, sep, after = rest.partition("bin/")
|
||||
if sep:
|
||||
tool = after
|
||||
|
||||
rlocation_path = os.path.join(repo_name, tool)
|
||||
tool = r.Rlocation(rlocation_path)
|
||||
|
||||
print(f"Running {tool} --version")
|
||||
try:
|
||||
res = subprocess.run(
|
||||
[tool, "--version"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
)
|
||||
print(res.stdout)
|
||||
except Exception as e:
|
||||
print(f"Failed to run {tool}: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_tools()
|
||||
@@ -17,7 +17,7 @@ load(
|
||||
load(
|
||||
":clang_detected_variables.bzl",
|
||||
"clang_bindir",
|
||||
"clang_include_dirs_list",
|
||||
"clang_include_dirs",
|
||||
"clang_resource_dir",
|
||||
"clang_version_for_cache",
|
||||
"llvm_bindir",
|
||||
@@ -40,7 +40,7 @@ def _impl(ctx):
|
||||
extra_cpp_features = [libcxx_feature(llvm_bindir, clang_bindir)],
|
||||
),
|
||||
action_configs = llvm_action_configs(llvm_bindir, clang_bindir),
|
||||
cxx_builtin_include_directories = clang_include_dirs_list + [
|
||||
cxx_builtin_include_directories = clang_include_dirs + [
|
||||
# Add Clang's resource directory to the end of the builtin include
|
||||
# directories to cover the use of sanitizer resource files by the
|
||||
# driver.
|
||||
@@ -92,7 +92,7 @@ def cc_local_toolchain_suite(name, configs):
|
||||
target_cpu = cpu,
|
||||
)
|
||||
cc_toolchain(
|
||||
name = config_name + "_tools",
|
||||
name = config_name + "_toolchain",
|
||||
all_files = ":" + name + "_empty",
|
||||
ar_files = ":" + name + "_empty",
|
||||
as_files = ":" + name + "_empty",
|
||||
@@ -109,7 +109,11 @@ def cc_local_toolchain_suite(name, configs):
|
||||
native.toolchain(
|
||||
name = config_name,
|
||||
exec_compatible_with = compatible_with,
|
||||
target_settings = [
|
||||
"@carbon//toolchain/install:is_bootstrap_stage_0",
|
||||
"@carbon//toolchain/install:not_runtimes_build",
|
||||
],
|
||||
target_compatible_with = compatible_with,
|
||||
toolchain = config_name + "_tools",
|
||||
toolchain = config_name + "_toolchain",
|
||||
toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
|
||||
)
|
||||
|
||||
@@ -226,7 +226,7 @@ def _configure_clang_toolchain_impl(repository_ctx):
|
||||
repository_ctx.attr._clang_detected_variables_template,
|
||||
substitutions = {
|
||||
"{CLANG_BINDIR}": str(clang.dirname),
|
||||
"{CLANG_INCLUDE_DIRS_LIST}": str(
|
||||
"{CLANG_INCLUDE_DIRS}": str(
|
||||
[str(path) for path in include_dirs],
|
||||
),
|
||||
"{CLANG_RESOURCE_DIR}": resource_dir,
|
||||
|
||||
@@ -14,5 +14,5 @@ clang_bindir = "{CLANG_BINDIR}"
|
||||
clang_version = {CLANG_VERSION}
|
||||
clang_version_for_cache = "{CLANG_VERSION_FOR_CACHE}"
|
||||
clang_resource_dir = "{CLANG_RESOURCE_DIR}"
|
||||
clang_include_dirs_list = {CLANG_INCLUDE_DIRS_LIST}
|
||||
clang_include_dirs = {CLANG_INCLUDE_DIRS}
|
||||
sysroot_dir = "{SYSROOT}"
|
||||
|
||||
@@ -13,8 +13,8 @@ load("@rules_python//python:defs.bzl", "py_test")
|
||||
filegroup(
|
||||
name = "non_test_cc_rules",
|
||||
data = [
|
||||
"//toolchain/install:carbon_toolchain_tar_gz_rule",
|
||||
"//toolchain/install:carbon_toolchain_tar_rule",
|
||||
"//toolchain/install:carbon_toolchain_tar",
|
||||
"//toolchain/install:carbon_toolchain_tar_gz",
|
||||
],
|
||||
tags = ["manual"],
|
||||
)
|
||||
|
||||
@@ -8,10 +8,6 @@ def _get_files(ctx):
|
||||
files = []
|
||||
for src in ctx.attr.srcs:
|
||||
files.extend([f.path for f in src[DefaultInfo].files.to_list()])
|
||||
files.extend([
|
||||
f.path
|
||||
for f in src[DefaultInfo].default_runfiles.files.to_list()
|
||||
])
|
||||
|
||||
if ctx.attr.strip_package_dir:
|
||||
# Files may or may not be prefixed with the bin directory, and then
|
||||
|
||||
@@ -643,7 +643,7 @@ auto DirRef::ReadlinkSlow(const std::filesystem::path& path)
|
||||
if (buffer_size == 0) {
|
||||
buffer_size = MinBufferSize;
|
||||
}
|
||||
large_buffer.resize(status.size());
|
||||
large_buffer.resize(buffer_size);
|
||||
ssize_t result =
|
||||
readlinkat(dfd_, path.c_str(), large_buffer.data(), large_buffer.size());
|
||||
if (result == -1) {
|
||||
|
||||
@@ -18,17 +18,18 @@ namespace Carbon {
|
||||
//
|
||||
// This provides a hashtable-specific extension point to implement equality
|
||||
// comparison within a hashtable key context. By default, it will use
|
||||
// `operator==` on the LHS and RHS operands. However, types can provide a
|
||||
// dedicated customization point by implementing a free function that can be
|
||||
// found by ADL for your type called `CarbonHashtableEq` with the following
|
||||
// signature:
|
||||
// `operator==` on the LHS and RHS operands if they are of the identical type.
|
||||
// However, types can provide a dedicated customization point by implementing a
|
||||
// free function that can be found by ADL for your type called
|
||||
// `CarbonHashtableEq` with the following signature:
|
||||
//
|
||||
// ```cpp
|
||||
// auto CarbonHashtableEq(const YourType& lhs, const YourType& rhs) -> bool;
|
||||
// ```
|
||||
//
|
||||
// Any such overload will be able to override the default we provide for types
|
||||
// that can compare with `==`.
|
||||
// that can compare with `==`. This overload may only compare two objects equal
|
||||
// if the hash of those two objects are identical.
|
||||
//
|
||||
// This library also provides any customization points for LLVM or standard
|
||||
// library types either lacking `operator==` or where that operator is not
|
||||
@@ -161,8 +162,37 @@ inline auto CarbonHashtableEq(const llvm::APFloat& lhs,
|
||||
return lhs.bitwiseIsEqual(rhs);
|
||||
}
|
||||
|
||||
template <typename LeftT, typename RightT>
|
||||
inline auto CarbonHashtableEq(const LeftT& lhs, const RightT& rhs) -> bool
|
||||
inline auto CarbonHashtableEq(llvm::StringRef lhs, const std::string& rhs)
|
||||
-> bool {
|
||||
return lhs == rhs;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline auto CarbonHashtableEq(llvm::MutableArrayRef<T> lhs,
|
||||
llvm::ArrayRef<T> rhs) -> bool {
|
||||
return lhs == rhs;
|
||||
}
|
||||
|
||||
template <typename LHS, typename RHS>
|
||||
inline auto CarbonHashtableEq(const LHS& lhs, const RHS& rhs) -> bool
|
||||
requires(requires {
|
||||
{ CarbonHashtableEq(rhs, lhs) } -> std::convertible_to<bool>;
|
||||
})
|
||||
{
|
||||
return CarbonHashtableEq(rhs, lhs);
|
||||
}
|
||||
|
||||
// Provides symmetric equality so the `CarbonHashtableEq` operands aren't
|
||||
// ordered.
|
||||
//
|
||||
// If this template proves problematic in any way, we can revisit it - the
|
||||
// `CarbonHashtableEq` functions don't really need to be symmetric, since they
|
||||
// generally represent an implicit conversion which is often only one-way (eg:
|
||||
// MutableArrayRef converts to ArrayRef, but not the other way around) - but
|
||||
// documenting/describing that asymmetry felt a little awkward too - so maybe
|
||||
// this template is an OK solution for now.
|
||||
template <typename T>
|
||||
inline auto CarbonHashtableEq(const T& lhs, const T& rhs) -> bool
|
||||
requires(requires {
|
||||
{ lhs == rhs } -> std::convertible_to<bool>;
|
||||
})
|
||||
|
||||
@@ -45,6 +45,10 @@ struct TestData : Printable<TestData> {
|
||||
|
||||
static_assert(std::is_copy_constructible_v<TestData>);
|
||||
|
||||
inline auto CarbonHashtableEq(int lhs, TestData rhs) -> bool {
|
||||
return lhs == rhs;
|
||||
}
|
||||
|
||||
// Non-trivial type for testing.
|
||||
struct MoveOnlyTestData : Printable<TestData> {
|
||||
int value;
|
||||
@@ -85,6 +89,10 @@ struct MoveOnlyTestData : Printable<TestData> {
|
||||
static_assert(!std::is_copy_constructible_v<MoveOnlyTestData>);
|
||||
static_assert(std::is_move_constructible_v<MoveOnlyTestData>);
|
||||
|
||||
inline auto CarbonHashtableEq(int lhs, const MoveOnlyTestData& rhs) -> bool {
|
||||
return lhs == rhs;
|
||||
}
|
||||
|
||||
// Test stateless key context that produces different hashes from normal.
|
||||
// Changing the hash values should result in test failures if the context ever
|
||||
// fails to be used.
|
||||
|
||||
@@ -45,7 +45,7 @@ interface DefaultOrUnformed {
|
||||
|
||||
final impl forall [T:! Default] T as DefaultOrUnformed {
|
||||
fn Op() -> Self {
|
||||
return T.Op();
|
||||
return T.(Default.Op)();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,17 @@ impl forall [T:! type, U:! ImplicitAs(T)] const U as ImplicitAs(T) {
|
||||
fn Convert[self: const U]() -> T { return (self as U).Convert(); }
|
||||
}
|
||||
|
||||
// `const` can be added to a pointer.
|
||||
// TODO: This is also provided as a builtin conversion. We provide it here so
|
||||
// that Optional(T*) can implicitly convert to Optional(const T*). See #5750.
|
||||
impl forall [T:! type] T* as ImplicitAs(const T*) {
|
||||
fn Convert[self: T*]() -> const T* = "pointer.unsafe_convert";
|
||||
}
|
||||
|
||||
impl forall [T:! type] T* as As(const T*) {
|
||||
fn Convert[self: T*]() -> const T* = "pointer.unsafe_convert";
|
||||
}
|
||||
|
||||
// Pointer types can be unsafely cast to other pointer types.
|
||||
// TODO: Should `unsafe as` be able to remove `const`?
|
||||
impl forall [T:! type, U:! type] T* as UnsafeAs(U*) {
|
||||
|
||||
@@ -27,6 +27,7 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
- [Return type](#return-type)
|
||||
- [Interfaces recap](#interfaces-recap)
|
||||
- [Facet types](#facet-types)
|
||||
- [Identified facet types](#identified-facet-types)
|
||||
- [Named constraints](#named-constraints)
|
||||
- [Subtyping between facet types](#subtyping-between-facet-types)
|
||||
- [Combining interfaces by anding facet types](#combining-interfaces-by-anding-facet-types)
|
||||
@@ -894,6 +895,25 @@ names of the facet type.
|
||||
This general structure of facet types holds not just for interfaces, but others
|
||||
described in the rest of this document.
|
||||
|
||||
### Identified facet types
|
||||
|
||||
A facet type is _identified_ if all the interfaces it references are declared
|
||||
and all of its named constraints are complete. An identified facet type is
|
||||
associated with a known set of interfaces.
|
||||
|
||||
A facet type is _partially identified_ if any of its named constraints are in
|
||||
the process of being defined. The interfaces associated with a partially
|
||||
identified facet type change as the named constraint is fully defined.
|
||||
|
||||
Types can implicitly convert to facet types when the requirements of the facet
|
||||
type are satisfied, but only if the facet type is identified. Attempting to
|
||||
convert to a facet type that is not identified is an error, since the
|
||||
requirements of the target facet type are not yet fully determined.
|
||||
|
||||
A facet with an unidentified or partially identified facet type may be converted
|
||||
_to_ other facet types. While its set of requirements are not fully determined,
|
||||
the requirements that are known at that time may be used.
|
||||
|
||||
## Named constraints
|
||||
|
||||
If the interfaces discussed above are the building blocks for facet types,
|
||||
@@ -933,6 +953,54 @@ constraint DrawVectorLegoFish {
|
||||
}
|
||||
```
|
||||
|
||||
However a named constraint may not refer to itself as a requirement, as that
|
||||
produces a cycle. In general, any use of named constraint inside its own
|
||||
definition is disallowed, except through the use of `Self`.
|
||||
|
||||
```carbon
|
||||
constraint SelfReferential {
|
||||
// ❌ Error: Can not refer to `SelfReferential` inside its own definition.
|
||||
require impls SelfReferential;
|
||||
}
|
||||
```
|
||||
|
||||
The facet type of `Self` is partially identified inside the definition of a
|
||||
named constraint. This allows `Self` to be converted to other facet types based
|
||||
on the known requirements of the partially identified facet type. Those
|
||||
requirements include any `require` ... `impls` statements written before the use
|
||||
of `Self`.
|
||||
|
||||
```carbon
|
||||
interface Z {}
|
||||
class UsesZ(T:! Z) {}
|
||||
|
||||
interface Y(T:! type) {}
|
||||
interface X {}
|
||||
|
||||
constraint Constraint {
|
||||
// The partially identified facet type of `Self` includes `Z` after this
|
||||
// statement.
|
||||
require impls Z;
|
||||
|
||||
// OK, the partially identified facet type of `Self` can convert to facet
|
||||
// type `Z` to match the parameter of `UsesZ`.
|
||||
require impls Y(UsesZ(Self));
|
||||
|
||||
// Also OK, as `Self` converts to `Z` again.
|
||||
require UsesZ(Self) impls X;
|
||||
}
|
||||
|
||||
constraint UseOfFutureRequirement {
|
||||
// ❌ Error: The partially identified facet type of `Self` does not yet
|
||||
// include `Z` since the requirement for `Z` comes later in the definition.
|
||||
require impls Y(UsesZ(Self));
|
||||
|
||||
// The partially identified facet type of `Self` includes `Z` after this
|
||||
// statement.
|
||||
require impls Z;
|
||||
}
|
||||
```
|
||||
|
||||
In general, Carbon makes no syntactic distinction between the uses of named
|
||||
constraints and interfaces, so one may be replaced with the other without
|
||||
affecting users. To accomplish this, Carbon allows a named constraint to be used
|
||||
@@ -1229,6 +1297,27 @@ var x: Iota;
|
||||
DoAdvanceAndEquals(x);
|
||||
```
|
||||
|
||||
The facet type at the end of a `require` ... `impls` statement must be
|
||||
identified.
|
||||
|
||||
```carbon
|
||||
constraint N;
|
||||
|
||||
interface I {
|
||||
// ❌ Error: Facet type `N` is not identified since the constraint `N` is not
|
||||
// complete.
|
||||
require impls N;
|
||||
}
|
||||
|
||||
interface J;
|
||||
|
||||
interface K {
|
||||
// OK, the facet type `J` is identified because the interface `J` is
|
||||
// declared.
|
||||
require impls J;
|
||||
}
|
||||
```
|
||||
|
||||
Like with named constraints, an interface implementation requirement doesn't by
|
||||
itself add any names to the interface, but again those can be added with `alias`
|
||||
declarations:
|
||||
|
||||
@@ -24,6 +24,7 @@ contributions.
|
||||
- [Optional tools](#optional-tools)
|
||||
- [Jujutsu (`jj`)](#jujutsu-jj)
|
||||
- [AI assistants](#ai-assistants)
|
||||
- [Running tests with AddressSanitizer (ASan)](#running-tests-with-addresssanitizer-asan)
|
||||
- [Manually building Clang and LLVM (not recommended)](#manually-building-clang-and-llvm-not-recommended)
|
||||
- [Troubleshooting build issues](#troubleshooting-build-issues)
|
||||
- [`bazel clean`](#bazel-clean)
|
||||
@@ -256,31 +257,50 @@ system that can be used instead of or alongside Git. See the
|
||||
for more information.
|
||||
|
||||
If you use `jj`, you may find the following configuration snippets (added to
|
||||
`~/.config/jj/config.toml`) helpful for your workflow:
|
||||
`jj config path --user`) helpful for your workflow:
|
||||
|
||||
```toml
|
||||
[aliases]
|
||||
```sh
|
||||
# Clean up untracked or abandoned commits.
|
||||
abandon-untagged = ["abandon", "all() & ~ancestors(@ | bookmarks() | remote_bookmarks())"]
|
||||
jj config set --user aliases.abandon-untagged '["abandon", "all() & ~ancestors(@ | bookmarks() | remote_bookmarks())"]'
|
||||
|
||||
[ui]
|
||||
# Use Git-style conflict markers, which VS Code can provide merge support for.
|
||||
conflict-marker-style = "git"
|
||||
jj config set --user ui.conflict-marker-style 'git'
|
||||
|
||||
[ui.diff]
|
||||
# Produce Git-compatible diff format.
|
||||
format = "git"
|
||||
jj config set --user ui.diff.format 'git'
|
||||
|
||||
[remotes.origin]
|
||||
# Automatically track all remote bookmarks.
|
||||
auto-track-bookmarks = "*"
|
||||
|
||||
[templates]
|
||||
# Automatically add a trailer to commits to indicate that they were AI-assisted.
|
||||
commit_trailers = '''
|
||||
"Assisted-by: My AI Tool"'''
|
||||
jj config set --user templates.commit_trailers "$(echo -e "'''\n\"Assisted-by: My AI Tool\"'''")"
|
||||
|
||||
# Make `jj bookmark advance` / `jj b a` only move bookmarks that point to
|
||||
# mutable commits, and move them to the most recent non-empty descendant.
|
||||
jj config set --user revsets.bookmark-advance-from 'heads(::to & bookmarks()) & ~immutable_heads()'
|
||||
jj config set --user revsets.bookmark-advance-to 'heads(::@ & ~(description("") & empty() & ~merges()))'
|
||||
```
|
||||
|
||||
<!-- google-doc-style-ignore -->
|
||||
|
||||
As well as this per-repository configuration (added to `jj config path --repo`)
|
||||
describing how your GitHub checkout is configured:
|
||||
|
||||
```sh
|
||||
# Automatically track all remote bookmarks.
|
||||
jj config set --repo remotes.origin.auto-track-bookmarks '*'
|
||||
|
||||
# `trunk()` is a jj builtin, but defaults to `main@upstream`.
|
||||
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)'
|
||||
```
|
||||
|
||||
<!-- google-doc-style-resume -->
|
||||
|
||||
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.
|
||||
|
||||
#### AI assistants
|
||||
|
||||
When using AI assistants and reviewing terminal commands, some commands which
|
||||
@@ -310,6 +330,20 @@ git show
|
||||
git status
|
||||
```
|
||||
|
||||
### Running tests with AddressSanitizer (ASan)
|
||||
|
||||
By default, the Bazel build mode for the toolchain does not enable
|
||||
AddressSanitizer (ASan). If you wish to enable ASan for local testing, you must
|
||||
pass the `--config=asan` flag explicitly:
|
||||
|
||||
```shell
|
||||
bazelisk test --config=asan //...
|
||||
```
|
||||
|
||||
Note that our Continuous Integration (CI) infrastructure runs a separate
|
||||
configuration for ASan to ensure test coverage without slowing down the default
|
||||
test cycle.
|
||||
|
||||
### Manually building Clang and LLVM (not recommended)
|
||||
|
||||
We primarily test against [apt.llvm.org](https://apt.llvm.org) and Homebrew
|
||||
|
||||
@@ -21,7 +21,7 @@ class BazelExampleTest(test_base.TestBase):
|
||||
test_base.TestBase.setUp(self)
|
||||
self.runfiles = runfiles.Create()
|
||||
self.install_module = self.runfiles.Rlocation(
|
||||
"carbon/toolchain/install/prefix/lib/carbon"
|
||||
"carbon/toolchain/install"
|
||||
)
|
||||
self.startup_flags = [
|
||||
"--ignore_all_rc_files",
|
||||
@@ -62,7 +62,9 @@ class BazelExampleTest(test_base.TestBase):
|
||||
# Retry transient errors with a brief delay.
|
||||
print(f"Attempt {attempt + 1} failed with exit code {exit_code}")
|
||||
time.sleep(attempt)
|
||||
|
||||
self.AssertExitCode(exit_code, 0, stderr)
|
||||
return stdout
|
||||
|
||||
def test_compile_lib(self) -> None:
|
||||
# TODO: Can remove this in favor of always running `test_run` if we can
|
||||
|
||||
@@ -40,9 +40,8 @@ fn Run() -> i32 {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// TODO: This initialization should zero-initialize the struct.
|
||||
// TODO: We should be able to use simply `= ()` or `= {}` to get the same effect.
|
||||
var address: Cpp.sockaddr_in6 = Cpp.sockaddr_in6.sockaddr_in6();
|
||||
// TODO: We should be able to use `= ()` or `= {}` to perform zero-initialization.
|
||||
var address: Cpp.sockaddr_in6;
|
||||
Cpp.memset(&address, 0, Cpp.SOCKADDR_IN_SIZE);
|
||||
// TODO: Should this be valid without a cast? `AF_INET6` is defined to an
|
||||
// integer literal in glibc at least, but we import it as an `i32`.
|
||||
|
||||
@@ -117,9 +117,9 @@ promise==2.3 \
|
||||
# via
|
||||
# gql
|
||||
# graphql-core
|
||||
requests==2.32.4 \
|
||||
--hash=sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c \
|
||||
--hash=sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422
|
||||
requests==2.33.0 \
|
||||
--hash=sha256:3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b \
|
||||
--hash=sha256:c7ebc5e8b0f21837386ad0e1c8fe8b829fa5f544d8df3b2253bff14ef29d7652
|
||||
# via gql
|
||||
rx==1.6.3 \
|
||||
--hash=sha256:ca71b65d0fc0603a3b5cfaa9e33f5ba81e4aae10a58491133595088d7734b2da
|
||||
|
||||
@@ -0,0 +1,253 @@
|
||||
# Identification of a named constraint during definition
|
||||
|
||||
<!--
|
||||
Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
||||
Exceptions. See /LICENSE for license information.
|
||||
SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
-->
|
||||
|
||||
[Pull request](https://github.com/carbon-language/carbon-lang/pull/6902)
|
||||
|
||||
<!-- toc -->
|
||||
|
||||
## Table of contents
|
||||
|
||||
- [Abstract](#abstract)
|
||||
- [Problem](#problem)
|
||||
- [Background](#background)
|
||||
- [Proposal](#proposal)
|
||||
- [Example with `Self`](#example-with-self)
|
||||
- [Motivating the partially identified state](#motivating-the-partially-identified-state)
|
||||
- [Disallowing conversions to incomplete named constraint](#disallowing-conversions-to-incomplete-named-constraint)
|
||||
- [Rationale](#rationale)
|
||||
- [Alternatives considered](#alternatives-considered)
|
||||
- [Considering a facet type of a named constraint to be identified in its definition](#considering-a-facet-type-of-a-named-constraint-to-be-identified-in-its-definition)
|
||||
- [Restricting to `Self`](#restricting-to-self)
|
||||
- [Allowing limited conversions to partially identified facet types.](#allowing-limited-conversions-to-partially-identified-facet-types)
|
||||
|
||||
<!-- tocstop -->
|
||||
|
||||
## Abstract
|
||||
|
||||
This proposal updates the criteria for when a facet type is considered
|
||||
"identified." Specifically, it relaxes the requirement for named constraints,
|
||||
allowing them to be incrementally identified inside the definition, rather than
|
||||
requiring them to be fully complete, when used through the `Self` facet. This
|
||||
change enables impl lookups with `Self` within a constraint's definition to
|
||||
correctly resolve witnesses based on prior `require impls` statements in the
|
||||
definition.
|
||||
|
||||
## Problem
|
||||
|
||||
Under the rules established in [Proposal #5168](/proposals/p5168.md), a facet
|
||||
type is identified only if all its referenced interfaces are declared and all
|
||||
its referenced named constraints are complete.
|
||||
|
||||
This definition creates a circularity problem during the definition of a named
|
||||
constraint. If a `require impls` statement inside a named constraint definition
|
||||
relies on an impl lookup with `Self`, that lookup will fail because the facet
|
||||
type of `Self` is not identified before the named constraint is complete. This
|
||||
prevents `require impls` statements in a named constraint from depending on
|
||||
earlier ones.
|
||||
|
||||
## Background
|
||||
|
||||
- [Proposal #5168](/proposals/p5168.md): Introduced rules for facet type
|
||||
identification and completion.
|
||||
|
||||
## Proposal
|
||||
|
||||
We propose redefining the identification criteria for facet types by introducing
|
||||
a new partially identified state.
|
||||
|
||||
A facet type can be in one of three states: unidentified, partially identified,
|
||||
or identified. A facet type's identifiedness is the minimum of that of its
|
||||
constituents:
|
||||
|
||||
- When a facet type refers to an interface, the facet type is not identified
|
||||
until the interface is declared, and is fully identified after. This
|
||||
includes inside the definition of the interface.
|
||||
- When a facet type refers to a named constraint, the facet type is not
|
||||
identified until the named constraint is declared. It is partially
|
||||
identified during the definition of the named constraint, and it is fully
|
||||
identified after.
|
||||
|
||||
The change from previous rules is that a facet type containing a named
|
||||
constraint is now partially identified inside the definition of that named
|
||||
constraint.
|
||||
|
||||
As in [#5168](/proposals/p5168.md), an `impl` declaration and `require`
|
||||
statement each requires its constraint to be identified.
|
||||
|
||||
We define the rules for facets in impl lookups, which are representable as
|
||||
`<self> as <target facet type>` conversions as follows:
|
||||
|
||||
- The target facet type of an impl lookup must be defined.
|
||||
- This allows the full set of interfaces to be known, which allows a
|
||||
stable ordering of witnesses for those interfaces to be produced by the
|
||||
impl lookup.
|
||||
- If the self is a facet, its facet type may be in any state of
|
||||
identifiedness.
|
||||
- The impl lookup may provide a witness from the facet type of self, using
|
||||
the known constraints of any partially identified or identified
|
||||
constituent of the facet type.
|
||||
|
||||
In particular, this means that `Self as I` inside the definition of a named
|
||||
constraint `N` may use any `require impls` statements before that use of `Self`
|
||||
in order to provide a witness for `I`.
|
||||
|
||||
To improve diagnostics, we also propose to disallow using a named constraint
|
||||
inside its own definition, except through the type of `Self`. Any other use is
|
||||
diagnosed as an error. This provides a clear error when a named constraint
|
||||
appears in the constraint of a `require` statement inside its definition.
|
||||
|
||||
### Example with `Self`
|
||||
|
||||
This change allows the compiler to treat a named constraint as partially
|
||||
identified for the purposes of impl lookup while it is still being typechecked.
|
||||
|
||||
As the compiler processes a series of `require impls` statements within a named
|
||||
constraint, the partially identified facet type of the named constraint, which
|
||||
can be accessed through `Self`, is built up incrementally. The partially
|
||||
identified facet type of `Self` will contain interfaces provided by
|
||||
`require impls` statements written prior to that use of `Self`. Thus later
|
||||
`require impls` statements can use the partially identified facet type of `Self`
|
||||
to find witnesses provided by earlier `require impls` statements during impl
|
||||
lookup.
|
||||
|
||||
The following example demonstrates a scenario that is currently invalid but
|
||||
would be enabled by this proposal:
|
||||
|
||||
```carbon
|
||||
interface Y {}
|
||||
|
||||
interface NeedsY(T:! Y) {}
|
||||
|
||||
constraint W {
|
||||
require impls Y;
|
||||
|
||||
// This requires an impl lookup where the query self value is `Self`
|
||||
// (which is of type `W`) and the query interface is `Y`. The lookup
|
||||
// requires identifying the facet type of `Self` to find a witness.
|
||||
// After this proposal, W is partially identified because it has begun being
|
||||
// defined. The lookup for `Self as Y` can now succeed due to the previous
|
||||
// `require impls` statement.
|
||||
require impls NeedsY(Self);
|
||||
}
|
||||
```
|
||||
|
||||
In this example, identifying `W` while it is being defined allows the lookup for
|
||||
`Self as Y` in order to form a facet value for `NeedsY` to succeed because the
|
||||
compiler knows `Self` (of type `W`) implements `Y` from the previous
|
||||
`require impls` statement.
|
||||
|
||||
### Motivating the partially identified state
|
||||
|
||||
If a facet type for a named constraint was considered identified (not partially
|
||||
identified) inside its definition, the following becomes possible:
|
||||
|
||||
```carbon
|
||||
constraint W {
|
||||
require C impls W;
|
||||
require impls Z;
|
||||
}
|
||||
```
|
||||
|
||||
This says that `C` must implement `W`, yet `W` is not fully defined. At that
|
||||
line `W` is still empty, so it places no requirements on `C`. The next line
|
||||
requires that anything implementing `W` must implement `Z`.
|
||||
|
||||
To prevent this, the constraint of a `require` statement must still be
|
||||
identified, and the facet type of the being-defined named constraint is only
|
||||
partially-identified.
|
||||
|
||||
Note this also disallows the use of `W` through an alias:
|
||||
|
||||
```carbon
|
||||
constraint W;
|
||||
alias X = W;
|
||||
constraint W {
|
||||
// Error: X refers to named constraint `W` that is not identified.
|
||||
require impls X;
|
||||
}
|
||||
```
|
||||
|
||||
### Disallowing conversions to incomplete named constraint
|
||||
|
||||
By requiring the target facet type of an impl lookup to be identified, we
|
||||
disallow an incomplete named constraint from being part of the target of an impl
|
||||
lookup.
|
||||
|
||||
The result of an impl lookup stores witnesses for the target facet type. If the
|
||||
target facet type was partially identified, the same facet type may have a
|
||||
different set of interfaces later. A change in the set of interfaces would
|
||||
invalidate the set of stored witnesses.
|
||||
|
||||
For example, if this was allowed:
|
||||
|
||||
```carbon
|
||||
interface Z {}
|
||||
interface X {}
|
||||
|
||||
constraint W;
|
||||
class C(T:! W) {}
|
||||
class D {}
|
||||
|
||||
interface Y(T:! type) {}
|
||||
|
||||
constraint W {
|
||||
require impls Z;
|
||||
// Constructs a `D as W` facet value.
|
||||
require impls Y(C(D));
|
||||
require impls X;
|
||||
}
|
||||
```
|
||||
|
||||
In this example the argument to `C` will be `D as W` which will store a witness
|
||||
for each interface in the identified facet type `W`. If we allow a partially
|
||||
identified facet type, then it will store a witness for the interface `Z`. But
|
||||
later uses of the facet value in `Y(C(D))` would expect witnesses for `Z`, `Y`
|
||||
and `X`, leading to unsoundness.
|
||||
|
||||
## Rationale
|
||||
|
||||
This change aligns with Carbon's goal of
|
||||
[Code that is easy to read, understand, and write](/docs/project/goals.md#code-that-is-easy-to-read-understand-and-write),
|
||||
by allowing expressive generics as they have been designed.
|
||||
|
||||
This follows the
|
||||
[Information accumulation](/docs/project/principles/information_accumulation.md)
|
||||
principle by increasing the information available to the program with each
|
||||
statement.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
### Considering a facet type of a named constraint to be identified in its definition
|
||||
|
||||
Initial versions of this proposal did not differentiate between partially
|
||||
identified and identified. This led to unsoundness by allowing conversion to a
|
||||
facet with the incomplete named constraint in the facet's type, as described in
|
||||
[Disallowing conversions to incomplete named constraint](#disallowing-conversions-to-incomplete-named-constraint).
|
||||
|
||||
### Restricting to `Self`
|
||||
|
||||
We considered restricting the use of the partially identified facet type to only
|
||||
be on the `Self` facet value. This provided a way to reduce exposure of the
|
||||
partially identified facet type. But by differentiating the the partially
|
||||
identified state from identified, we can form the rules around the state of the
|
||||
facet type instead of the identity of the facet.
|
||||
|
||||
### Allowing limited conversions to partially identified facet types.
|
||||
|
||||
We considered allowing conversions from `N & J` to `N & K` where `N` is
|
||||
partially identified, and `J` and `K` are identified.
|
||||
|
||||
It seems possible to support this for symbolic facets, by not storing the set of
|
||||
witnesses in the facet value. If the facet type is partially identified, we can
|
||||
defer the collection of witnesses, and just store the facet types that the facet
|
||||
was converted from. In this model, the facet type itself acts as a type of
|
||||
witness that we will will be able to find a witness later once the facet type is
|
||||
identified.
|
||||
|
||||
We leave this to a future proposal if and when we find this additional
|
||||
complexity worth adding to the language model.
|
||||
@@ -24,7 +24,6 @@ SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import Any, Dict
|
||||
@@ -78,6 +77,9 @@ def _build_generated_files(
|
||||
# We also need the Bazel C++ runfiles that aren't "generated", but are
|
||||
# not linked into place until built.
|
||||
+ ["@bazel_tools//tools/cpp/runfiles:runfiles"]
|
||||
# Also include any deps that require `_virtual_includes` to be fully
|
||||
# populated through a special C++ rule.
|
||||
+ ["//scripts:deps_for_clangd_tidy"],
|
||||
)
|
||||
|
||||
|
||||
@@ -97,44 +99,6 @@ def _get_config_for_entry(entry: Dict[str, Any]) -> str:
|
||||
return str(obj_file.split("/")[1])
|
||||
|
||||
|
||||
def _filter_compilation_database(file_path: str) -> None:
|
||||
"""Filters out duplicate exec-config entries from the database."""
|
||||
print("Filtering out duplicate exec-configuration entries...")
|
||||
try:
|
||||
with open(file_path, "r") as f:
|
||||
commands = json.load(f)
|
||||
except FileNotFoundError:
|
||||
print(f"Error: The file '{file_path}' was not found.")
|
||||
sys.exit(1)
|
||||
except json.JSONDecodeError:
|
||||
print(f"Error: The file '{file_path}' is not a valid JSON file.")
|
||||
sys.exit(1)
|
||||
|
||||
# We want to skip compiles that were in the "exec" configuration for tools.
|
||||
# Because we generate compile commands for every bazel cc_* target in the
|
||||
# main configuration, even if only used by tools, their sources should be
|
||||
# covered and the exec configuration would simply be a duplicate.
|
||||
#
|
||||
# Detecting this based on the `-exec-` string in the configuration name of
|
||||
# the directory is a bit of a hack, but even using the `--notool_deps`
|
||||
# argument, Bazel seems to sometimes include this configuration in the query
|
||||
# that produces the compilation database.
|
||||
filtered_commands = [
|
||||
entry
|
||||
for entry in commands
|
||||
if "-exec-" not in _get_config_for_entry(entry)
|
||||
]
|
||||
|
||||
with open(file_path, "w") as f:
|
||||
# Use indent=4 for a human-readable, pretty-printed output file
|
||||
json.dump(filtered_commands, f, indent=4)
|
||||
print(
|
||||
"Filtered out "
|
||||
f"{len(commands) - len(filtered_commands)} "
|
||||
"duplicate entries..."
|
||||
)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(
|
||||
description=__doc__,
|
||||
@@ -179,17 +143,12 @@ def main() -> None:
|
||||
]
|
||||
+ args.extra_bazel_flag
|
||||
+ [
|
||||
"@hedron_compile_commands//:refresh_all",
|
||||
"//:generate_compile_commands",
|
||||
"--",
|
||||
]
|
||||
+ args.extra_bazel_flag
|
||||
+ [
|
||||
"--notool_deps",
|
||||
]
|
||||
+ [f"--extra_aquery_arg={arg}" for arg in args.extra_bazel_flag]
|
||||
)
|
||||
|
||||
_filter_compilation_database("compile_commands.json")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -295,6 +295,18 @@ def main() -> None:
|
||||
all_missing_deps: list[tuple[str, set[str]]] = []
|
||||
any_ambiguous = False
|
||||
for rule_name, rule in carbon_rules.items():
|
||||
# Skip rules building runtimes as the rules that provide their sources
|
||||
# are not analyzed by this script.
|
||||
if rule_name in [
|
||||
"//toolchain/install:builtins",
|
||||
"//toolchain/install:builtins_internal",
|
||||
"//toolchain/install:libc_internal_libcxx",
|
||||
"//toolchain/install:libcxx",
|
||||
"//toolchain/install:libcxxabi_internal",
|
||||
"//toolchain/install:libcxxabi",
|
||||
"//toolchain/install:libunwind",
|
||||
]:
|
||||
continue
|
||||
missing_deps, ambiguous = get_missing_deps(
|
||||
header_to_rule_map, generated_files, rule
|
||||
)
|
||||
|
||||
@@ -18,7 +18,7 @@ $(git diff --summary "${COMMIT_SHA}^!")
|
||||
|
||||
# Setup global git configuration.
|
||||
GIT_USERNAME="CarbonInfraBot"
|
||||
git config --global user.email "carbon-external-infra@google.com"
|
||||
git config --global user.email "infra-role@carbon-lang.dev"
|
||||
git config --global user.name "$GIT_USERNAME"
|
||||
|
||||
declare -A MIRRORS
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ run_tool(
|
||||
name = "carbon",
|
||||
data = ["//toolchain/install:install_data"],
|
||||
env = cc_env(),
|
||||
tool = "//toolchain/install:prefix/bin/carbon",
|
||||
tool = "//toolchain/install:carbon-busybox",
|
||||
)
|
||||
|
||||
# A convenience target for running the toolchain with the full prelude
|
||||
|
||||
@@ -21,8 +21,7 @@
|
||||
namespace Carbon {
|
||||
|
||||
// The location within our Bazel output tree of the install root.
|
||||
static constexpr llvm::StringLiteral BazelRoot =
|
||||
"carbon/toolchain/install/prefix/lib/carbon/";
|
||||
static constexpr llvm::StringLiteral BazelRoot = "carbon/toolchain/install/";
|
||||
|
||||
// Path within an install root for our marker of a valid install.
|
||||
static constexpr llvm::StringLiteral MarkerPath = "carbon_install.txt";
|
||||
@@ -84,7 +83,7 @@ auto InstallPaths::ReadClangHeadersManifest() const
|
||||
// root. Consider whether this manifest should be within the install or
|
||||
// consider moving the code to access it to be separate and specific to the
|
||||
// infrastructure needing it.
|
||||
return ReadManifest(root_ / "../../..", "clang_headers_manifest.txt");
|
||||
return ReadManifest(root_, "clang_headers_manifest.txt");
|
||||
}
|
||||
|
||||
auto InstallPaths::ReadManifest(std::filesystem::path manifest_path,
|
||||
|
||||
@@ -148,7 +148,7 @@ class InstallPaths {
|
||||
auto SetError(llvm::Twine message) -> void;
|
||||
|
||||
// Check that the install paths have a marker file at
|
||||
// `root()/lib/carbon/carbon_install.txt". If not, calls `SetError` with the
|
||||
// `root()/carbon_install.txt". If not, calls `SetError` with the
|
||||
// relevant error message.
|
||||
auto CheckMarkerFile() -> void;
|
||||
|
||||
@@ -162,7 +162,7 @@ class InstallPaths {
|
||||
//
|
||||
// When run from Bazel (for example, in unit tests or development binaries)
|
||||
// this will look like:
|
||||
// `bazel-bin/some/bazel/target.runfiles/_main/toolchain/install/prefix/lib/carbon`
|
||||
// `bazel-bin/some/bazel/target.runfiles/_main/toolchain/install`
|
||||
//
|
||||
// When installed, it's expected to be similar to the CMake install prefix,
|
||||
// followed by `lib/carbon`:
|
||||
@@ -177,7 +177,7 @@ class InstallPaths {
|
||||
// our installation to behave in a similar and compatible way.
|
||||
//
|
||||
// The hierarchy of files beneath the install root can be found in the
|
||||
// BUILD's `install_dirs` entry for `lib/carbon`.
|
||||
// BUILD rules in `//toolchain/install`.
|
||||
std::filesystem::path root_;
|
||||
|
||||
// The opened root directory.
|
||||
|
||||
@@ -32,7 +32,6 @@ namespace {
|
||||
|
||||
using ::bazel::tools::cpp::runfiles::Runfiles;
|
||||
using ::testing::_;
|
||||
using ::testing::EndsWith;
|
||||
using ::testing::Eq;
|
||||
using ::testing::HasSubstr;
|
||||
using Testing::IsSuccess;
|
||||
@@ -61,14 +60,6 @@ class InstallPathsTest : public ::testing::Test {
|
||||
ASSERT_THAT(root_result, IsSuccess(_));
|
||||
Filesystem::Dir root = *std::move(root_result);
|
||||
|
||||
// Check that the root is located in the expected part of the FHS layout.
|
||||
// TODO: Adjust this to work equally well on Windows.
|
||||
EXPECT_THAT(root_path.native(), EndsWith("lib/carbon/"));
|
||||
EXPECT_THAT(
|
||||
root.Access("../../bin/carbon", Filesystem::AccessCheckFlags::Execute),
|
||||
IsSuccess(Eq(true)))
|
||||
<< "path: " << (root_path / "../../bin/carbon");
|
||||
|
||||
std::filesystem::path core_package_path = paths.core_package();
|
||||
ASSERT_THAT(core_package_path, StartsWith(root_path));
|
||||
EXPECT_THAT(Filesystem::Cwd().Access(core_package_path / "prelude.carbon"),
|
||||
@@ -93,8 +84,8 @@ class InstallPathsTest : public ::testing::Test {
|
||||
};
|
||||
|
||||
TEST_F(InstallPathsTest, RootBusybox) {
|
||||
std::string installed_busybox_path = test_runfiles_->Rlocation(
|
||||
"carbon/toolchain/install/prefix/lib/carbon/carbon-busybox");
|
||||
std::string installed_busybox_path =
|
||||
test_runfiles_->Rlocation("carbon/toolchain/install/carbon-busybox");
|
||||
|
||||
auto paths = InstallPaths::MakeExeRelative(installed_busybox_path);
|
||||
ASSERT_THAT(paths.error(), Eq(std::nullopt)) << *paths.error();
|
||||
@@ -102,8 +93,8 @@ TEST_F(InstallPathsTest, RootBusybox) {
|
||||
}
|
||||
|
||||
TEST_F(InstallPathsTest, RootExplicit) {
|
||||
std::string marker_path = test_runfiles_->Rlocation(
|
||||
"carbon/toolchain/install/prefix/lib/carbon/carbon_install.txt");
|
||||
std::string marker_path =
|
||||
test_runfiles_->Rlocation("carbon/toolchain/install/carbon_install.txt");
|
||||
|
||||
llvm::StringRef root_path = marker_path;
|
||||
CARBON_CHECK(root_path.consume_back("carbon_install.txt"),
|
||||
|
||||
@@ -62,16 +62,16 @@ RUNTIMES_TEXTUAL_SRCS_FILEGROUPS = [
|
||||
]
|
||||
|
||||
RUNTIMES_PREFIXES = {
|
||||
"libcxx_hdrs": "libcxx/",
|
||||
"libcxx_linux_srcs": "libcxx/",
|
||||
"libcxx_macos_srcs": "libcxx/",
|
||||
"libcxx_shared_headers_hdrs": "libc/internal/",
|
||||
"libcxx_win32_srcs": "libcxx/",
|
||||
"libcxxabi_hdrs": "libcxxabi/",
|
||||
"libcxxabi_srcs": "libcxxabi/",
|
||||
"libcxxabi_textual_srcs": "libcxxabi/",
|
||||
"libunwind_hdrs": "libunwind/",
|
||||
"libunwind_srcs": "libunwind/",
|
||||
"libcxx_hdrs": "runtimes/libcxx/",
|
||||
"libcxx_linux_srcs": "runtimes/libcxx/",
|
||||
"libcxx_macos_srcs": "runtimes/libcxx/",
|
||||
"libcxx_shared_headers_hdrs": "runtimes/libc/internal/",
|
||||
"libcxx_win32_srcs": "runtimes/libcxx/",
|
||||
"libcxxabi_hdrs": "runtimes/libcxxabi/",
|
||||
"libcxxabi_srcs": "runtimes/libcxxabi/",
|
||||
"libcxxabi_textual_srcs": "runtimes/libcxxabi/",
|
||||
"libunwind_hdrs": "runtimes/libunwind/",
|
||||
"libunwind_srcs": "runtimes/libunwind/",
|
||||
}
|
||||
|
||||
def _get_name(target):
|
||||
@@ -94,7 +94,7 @@ def _builtins_path(file):
|
||||
path = path.removeprefix("compiler-rt/lib/")
|
||||
if not path.startswith("builtins/"):
|
||||
fail("Not a builtins-relative path for: {0}".format(file.path))
|
||||
return path
|
||||
return "runtimes/" + path
|
||||
|
||||
def _runtimes_path(file):
|
||||
"""Returns the install path for a file in a normal runtimes library."""
|
||||
|
||||
@@ -232,7 +232,7 @@ class ValueStore
|
||||
|
||||
auto GetIdTag() const -> IdTagType { return tag_; }
|
||||
auto GetRawIndex(IdT id) const -> int32_t {
|
||||
CARBON_DCHECK(id.index >= 0, "{0}", index);
|
||||
CARBON_DCHECK(id.index >= 0, "{0}", id.index);
|
||||
auto index = tag_.Remove(id);
|
||||
#ifndef NDEBUG
|
||||
if (index >= size_) {
|
||||
|
||||
@@ -26,6 +26,7 @@ cc_library(
|
||||
"cpp/constant.cpp",
|
||||
"cpp/context.cpp",
|
||||
"cpp/custom_type_mapping.cpp",
|
||||
"cpp/export.cpp",
|
||||
"cpp/generate_ast.cpp",
|
||||
"cpp/impl_lookup.cpp",
|
||||
"cpp/import.cpp",
|
||||
@@ -66,6 +67,7 @@ cc_library(
|
||||
"pattern.cpp",
|
||||
"pattern_match.cpp",
|
||||
"pointer_dereference.cpp",
|
||||
"require_impls_stack.cpp",
|
||||
"return.cpp",
|
||||
"scope_stack.cpp",
|
||||
"subst.cpp",
|
||||
@@ -87,6 +89,7 @@ cc_library(
|
||||
"cpp/constant.h",
|
||||
"cpp/context.h",
|
||||
"cpp/custom_type_mapping.h",
|
||||
"cpp/export.h",
|
||||
"cpp/generate_ast.h",
|
||||
"cpp/impl_lookup.h",
|
||||
"cpp/import.h",
|
||||
@@ -134,6 +137,7 @@ cc_library(
|
||||
"pending_block.h",
|
||||
"pointer_dereference.h",
|
||||
"region_stack.h",
|
||||
"require_impls_stack.h",
|
||||
"return.h",
|
||||
"scope_index.h",
|
||||
"scope_stack.h",
|
||||
@@ -181,6 +185,7 @@ cc_library(
|
||||
"//toolchain/sem_ir:expr_info",
|
||||
"//toolchain/sem_ir:file",
|
||||
"//toolchain/sem_ir:formatter",
|
||||
"//toolchain/sem_ir:mangler",
|
||||
"//toolchain/sem_ir:typed_insts",
|
||||
"@llvm-project//clang:ast",
|
||||
"@llvm-project//clang:basic",
|
||||
|
||||
+34
-28
@@ -24,56 +24,59 @@ auto PerformAction(Context& context, SemIR::LocId loc_id,
|
||||
.source_id = action.inst_id});
|
||||
}
|
||||
|
||||
static auto OperandIsDependent(Context& context, SemIR::ConstantId const_id)
|
||||
-> bool {
|
||||
static auto OperandDependence(Context& context, SemIR::ConstantId const_id)
|
||||
-> SemIR::ConstantDependence {
|
||||
// A type operand makes the instruction dependent if it is a
|
||||
// template-dependent constant.
|
||||
if (!const_id.is_symbolic()) {
|
||||
return false;
|
||||
return SemIR::ConstantDependence::None;
|
||||
}
|
||||
return context.constant_values().GetSymbolicConstant(const_id).dependence ==
|
||||
SemIR::ConstantDependence::Template;
|
||||
return context.constant_values().GetSymbolicConstant(const_id).dependence;
|
||||
}
|
||||
|
||||
auto OperandIsDependent(Context& context, SemIR::TypeId type_id) -> bool {
|
||||
auto OperandDependence(Context& context, SemIR::TypeId type_id)
|
||||
-> SemIR::ConstantDependence {
|
||||
// A type operand makes the instruction dependent if it is a
|
||||
// template-dependent type.
|
||||
return OperandIsDependent(context, context.types().GetConstantId(type_id));
|
||||
return OperandDependence(context, context.types().GetConstantId(type_id));
|
||||
}
|
||||
|
||||
auto OperandIsDependent(Context& context, SemIR::InstId inst_id) -> bool {
|
||||
auto OperandDependence(Context& context, SemIR::InstId inst_id)
|
||||
-> SemIR::ConstantDependence {
|
||||
// An instruction operand makes the instruction dependent if its type or
|
||||
// constant value is dependent.
|
||||
return OperandIsDependent(context, context.insts().Get(inst_id).type_id()) ||
|
||||
OperandIsDependent(context, context.constant_values().Get(inst_id));
|
||||
return std::max(
|
||||
OperandDependence(context, context.insts().Get(inst_id).type_id()),
|
||||
OperandDependence(context, context.constant_values().Get(inst_id)));
|
||||
}
|
||||
|
||||
auto OperandIsDependent(Context& context, SemIR::TypeInstId inst_id) -> bool {
|
||||
auto OperandDependence(Context& context, SemIR::TypeInstId inst_id)
|
||||
-> SemIR::ConstantDependence {
|
||||
// An instruction operand makes the instruction dependent if its type or
|
||||
// constant value is dependent. TypeInstId has type `TypeType` which is
|
||||
// concrete, so we only need to look at the constant value.
|
||||
return OperandIsDependent(context, context.constant_values().Get(inst_id));
|
||||
return OperandDependence(context, context.constant_values().Get(inst_id));
|
||||
}
|
||||
|
||||
static auto OperandIsDependent(Context& context, SemIR::Inst::ArgAndKind arg)
|
||||
-> bool {
|
||||
static auto OperandDependence(Context& context, SemIR::Inst::ArgAndKind arg)
|
||||
-> SemIR::ConstantDependence {
|
||||
CARBON_KIND_SWITCH(arg) {
|
||||
case CARBON_KIND(SemIR::InstId inst_id): {
|
||||
return OperandIsDependent(context, inst_id);
|
||||
return OperandDependence(context, inst_id);
|
||||
}
|
||||
|
||||
case CARBON_KIND(SemIR::MetaInstId inst_id): {
|
||||
return OperandIsDependent(context, inst_id);
|
||||
return OperandDependence(context, inst_id);
|
||||
}
|
||||
|
||||
case CARBON_KIND(SemIR::TypeInstId inst_id): {
|
||||
return OperandIsDependent(context, inst_id);
|
||||
return OperandDependence(context, inst_id);
|
||||
}
|
||||
|
||||
case SemIR::IdKind::None:
|
||||
case SemIR::IdKind::For<SemIR::AbsoluteInstId>:
|
||||
case SemIR::IdKind::For<SemIR::NameId>:
|
||||
return false;
|
||||
return SemIR::ConstantDependence::None;
|
||||
|
||||
default:
|
||||
// TODO: Properly handle different argument kinds.
|
||||
@@ -81,18 +84,20 @@ static auto OperandIsDependent(Context& context, SemIR::Inst::ArgAndKind arg)
|
||||
}
|
||||
}
|
||||
|
||||
auto ActionIsDependent(Context& context, SemIR::Inst action_inst) -> bool {
|
||||
auto ActionIsPerformable(Context& context, SemIR::Inst action_inst) -> bool {
|
||||
if (auto refine_action = action_inst.TryAs<SemIR::RefineTypeAction>()) {
|
||||
// `RefineTypeAction` can be performed whenever the type is non-dependent,
|
||||
// even if we don't know the instruction yet.
|
||||
return OperandIsDependent(context, refine_action->inst_type_inst_id);
|
||||
// `RefineTypeAction` can be performed whenever the type is not template-
|
||||
// dependent, even if we don't know the instruction yet.
|
||||
return OperandDependence(context, refine_action->inst_type_inst_id) <
|
||||
SemIR::ConstantDependence::Template;
|
||||
}
|
||||
|
||||
if (OperandIsDependent(context, action_inst.type_id())) {
|
||||
return true;
|
||||
}
|
||||
return OperandIsDependent(context, action_inst.arg0_and_kind()) ||
|
||||
OperandIsDependent(context, action_inst.arg1_and_kind());
|
||||
return OperandDependence(context, action_inst.type_id()) <
|
||||
SemIR::ConstantDependence::Template &&
|
||||
OperandDependence(context, action_inst.arg0_and_kind()) <
|
||||
SemIR::ConstantDependence::Template &&
|
||||
OperandDependence(context, action_inst.arg1_and_kind()) <
|
||||
SemIR::ConstantDependence::Template;
|
||||
}
|
||||
|
||||
static auto AddDependentActionSpliceImpl(Context& context,
|
||||
@@ -129,7 +134,8 @@ static auto RefineOperand(Context& context, SemIR::LocId loc_id,
|
||||
|
||||
// If the type of the action argument is dependent, refine to an instruction
|
||||
// with a concrete type.
|
||||
if (OperandIsDependent(context, inst.type_id())) {
|
||||
if (OperandDependence(context, inst.type_id()) ==
|
||||
SemIR::ConstantDependence::Template) {
|
||||
auto type_inst_id = context.types().GetTypeInstId(inst.type_id());
|
||||
inst_id = AddDependentActionSpliceImpl(
|
||||
context,
|
||||
|
||||
+15
-17
@@ -28,21 +28,21 @@ auto PerformAction(Context& context, SemIR::LocId loc_id,
|
||||
auto PerformAction(Context& context, SemIR::LocId loc_id,
|
||||
SemIR::RefineTypeAction action) -> SemIR::InstId;
|
||||
|
||||
// Determines whether the given action depends on a template parameter in a way
|
||||
// that means it cannot be performed immediately.
|
||||
auto ActionIsDependent(Context& context, SemIR::Inst action_inst) -> bool;
|
||||
// Determines whether the given action can be performed immediately (i.e.
|
||||
// whether it is non-template-dependent).
|
||||
auto ActionIsPerformable(Context& context, SemIR::Inst action_inst) -> bool;
|
||||
|
||||
// Determines whether the given action operand depends on a template parameter
|
||||
// in a way that means the action cannot be performed immediately.
|
||||
auto OperandIsDependent(Context& context, SemIR::InstId inst_id) -> bool;
|
||||
// Returns the constant-dependence of `inst_id` (i.e. the maximum of the
|
||||
// constant-dependences of its type and its value).
|
||||
auto OperandDependence(Context& context, SemIR::InstId inst_id)
|
||||
-> SemIR::ConstantDependence;
|
||||
auto OperandDependence(Context& context, SemIR::TypeInstId inst_id)
|
||||
-> SemIR::ConstantDependence;
|
||||
|
||||
// Determines whether the given action operand depends on a template parameter
|
||||
// in a way that means the action cannot be performed immediately.
|
||||
auto OperandIsDependent(Context& context, SemIR::TypeInstId inst_id) -> bool;
|
||||
|
||||
// Determines whether the given type depends on a template parameter
|
||||
// in a way that means the action cannot be performed immediately.
|
||||
auto OperandIsDependent(Context& context, SemIR::TypeId type_id) -> bool;
|
||||
// Returns the constant-dependence of `type_id` (i.e. the constant-dependence
|
||||
// of the corresponding type constant).
|
||||
auto OperandDependence(Context& context, SemIR::TypeId type_id)
|
||||
-> SemIR::ConstantDependence;
|
||||
|
||||
// Adds an instruction to the current block to splice in the result of
|
||||
// performing a dependent action.
|
||||
@@ -66,9 +66,7 @@ template <typename ActionT>
|
||||
auto HandleAction(Context& context, SemIR::LocId loc_id, ActionT action_inst,
|
||||
SemIR::TypeInstId result_type_inst_id =
|
||||
SemIR::TypeInstId::None) -> SemIR::InstId {
|
||||
if (ActionIsDependent(context, action_inst) ||
|
||||
(result_type_inst_id.has_value() &&
|
||||
OperandIsDependent(context, result_type_inst_id))) {
|
||||
if (!ActionIsPerformable(context, action_inst)) {
|
||||
return AddDependentActionSplice(
|
||||
context, SemIR::LocIdAndInst(loc_id, action_inst), result_type_inst_id);
|
||||
}
|
||||
@@ -93,7 +91,7 @@ auto EndPerformDelayedAction(Context& context, SemIR::InstId result_id)
|
||||
template <typename ActionT>
|
||||
auto PerformDelayedAction(Context& context, SemIR::LocId loc_id,
|
||||
ActionT action_inst) -> SemIR::InstId {
|
||||
if (ActionIsDependent(context, action_inst)) {
|
||||
if (!ActionIsPerformable(context, action_inst)) {
|
||||
return SemIR::InstId::None;
|
||||
}
|
||||
Internal::BeginPerformDelayedAction(context);
|
||||
|
||||
@@ -280,11 +280,16 @@ auto PerformCallToFunction(Context& context, SemIR::LocId loc_id,
|
||||
BuildNameRef(context, loc_id, callee.name_id, callee.thunk_decl_id(),
|
||||
callee_function.enclosing_specific_id);
|
||||
|
||||
auto param_pattern_ids =
|
||||
context.inst_blocks().Get(context.functions()
|
||||
.Get(callee_function.function_id)
|
||||
.param_patterns_id);
|
||||
|
||||
// This recurses back into `PerformCall`. However, we never form a thunk
|
||||
// to a thunk, so we only recurse once.
|
||||
return PerformThunkCall(context, loc_id, callee_function.function_id,
|
||||
context.inst_blocks().Get(converted_args_id),
|
||||
thunk_ref_id);
|
||||
return PerformThunkCall(
|
||||
context, loc_id, callee_function.function_id, param_pattern_ids,
|
||||
context.inst_blocks().Get(converted_args_id), thunk_ref_id);
|
||||
}
|
||||
|
||||
case SemIR::Function::SpecialFunctionKind::HasCppThunk: {
|
||||
@@ -295,7 +300,8 @@ auto PerformCallToFunction(Context& context, SemIR::LocId loc_id,
|
||||
|
||||
case SemIR::Function::SpecialFunctionKind::None:
|
||||
case SemIR::Function::SpecialFunctionKind::Builtin:
|
||||
case SemIR::Function::SpecialFunctionKind::CoreWitness: {
|
||||
case SemIR::Function::SpecialFunctionKind::CoreWitness:
|
||||
case SemIR::Function::SpecialFunctionKind::CppThunk: {
|
||||
return GetOrAddInst<SemIR::Call>(context, loc_id,
|
||||
{.type_id = return_type_id,
|
||||
.callee_id = callee_id,
|
||||
|
||||
@@ -355,11 +355,12 @@ auto CheckUnit::ImportOtherPackages(SemIR::TypeId namespace_type_id) -> void {
|
||||
auto import_ir_inst_id =
|
||||
context_.import_ir_insts().Add(SemIR::ImportIRInst(
|
||||
SemIR::ImportIRId::ApiForImpl, api_imports->import_decl_id));
|
||||
import_decl_id =
|
||||
AddInst(context_, MakeImportedLocIdAndInst<SemIR::ImportDecl>(
|
||||
context_, import_ir_inst_id,
|
||||
{.package_id = SemIR::NameId::ForPackageName(
|
||||
api_imports_entry.first)}));
|
||||
import_decl_id = AddInst(
|
||||
context_,
|
||||
SemIR::LocIdAndInst::RuntimeVerified(
|
||||
context_.sem_ir(), import_ir_inst_id,
|
||||
SemIR::ImportDecl{.package_id = SemIR::NameId::ForPackageName(
|
||||
api_imports_entry.first)}));
|
||||
package_id = api_imports_entry.first;
|
||||
}
|
||||
has_load_error |= api_imports->has_load_error;
|
||||
@@ -529,20 +530,19 @@ auto CheckUnit::CheckPoisonedConcreteImplLookupQueries() -> void {
|
||||
auto poisoned_queries =
|
||||
std::exchange(context_.poisoned_concrete_impl_lookup_queries(), {});
|
||||
for (const auto& poison : poisoned_queries) {
|
||||
auto witness_result = EvalLookupSingleImplWitness(
|
||||
context_, poison.loc_id, poison.query, poison.query.query_self_inst_id,
|
||||
auto found_witness_id = EvalLookupSingleFinalWitness(
|
||||
context_, poison.loc_id, poison.query, SemIR::InstId::None,
|
||||
EvalImplLookupMode::RecheckPoisonedLookup);
|
||||
CARBON_CHECK(witness_result.has_final_value());
|
||||
auto found_witness_id = witness_result.final_witness();
|
||||
if (found_witness_id == SemIR::ErrorInst::InstId) {
|
||||
CARBON_CHECK(found_witness_id.has_value());
|
||||
if (found_witness_id == SemIR::ErrorInst::ConstantId) {
|
||||
// Errors may have been diagnosed with the impl used in the poisoned query
|
||||
// in the meantime (such as a missing definition).
|
||||
continue;
|
||||
}
|
||||
if (found_witness_id != poison.impl_witness) {
|
||||
auto witness_to_impl_id = [&](SemIR::InstId witness_id) {
|
||||
auto table_id = context_.insts()
|
||||
.GetAs<SemIR::ImplWitness>(witness_id)
|
||||
if (found_witness_id != poison.witness_id) {
|
||||
auto witness_to_impl_id = [&](SemIR::ConstantId witness_id) {
|
||||
auto table_id = context_.constant_values()
|
||||
.GetInstAs<SemIR::ImplWitness>(witness_id)
|
||||
.witness_table_id;
|
||||
return context_.insts()
|
||||
.GetAs<SemIR::ImplWitnessTable>(table_id)
|
||||
@@ -554,7 +554,7 @@ auto CheckUnit::CheckPoisonedConcreteImplLookupQueries() -> void {
|
||||
auto bad_impl_id = witness_to_impl_id(found_witness_id);
|
||||
const auto& bad_impl = context_.impls().Get(bad_impl_id);
|
||||
|
||||
auto prev_impl_id = witness_to_impl_id(poison.impl_witness);
|
||||
auto prev_impl_id = witness_to_impl_id(poison.witness_id);
|
||||
const auto& prev_impl = context_.impls().Get(prev_impl_id);
|
||||
|
||||
CARBON_DIAGNOSTIC(
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "toolchain/check/node_stack.h"
|
||||
#include "toolchain/check/param_and_arg_refs_stack.h"
|
||||
#include "toolchain/check/region_stack.h"
|
||||
#include "toolchain/check/require_impls_stack.h"
|
||||
#include "toolchain/check/scope_stack.h"
|
||||
#include "toolchain/diagnostics/emitter.h"
|
||||
#include "toolchain/parse/node_ids.h"
|
||||
@@ -125,7 +126,7 @@ class Context {
|
||||
return field_decls_stack_;
|
||||
}
|
||||
|
||||
auto require_impls_stack() -> ArrayStack<SemIR::RequireImplsId>& {
|
||||
auto require_impls_stack() -> RequireImplsStack& {
|
||||
return require_impls_stack_;
|
||||
}
|
||||
|
||||
@@ -221,6 +222,9 @@ class Context {
|
||||
SemIR::ConstantId query_facet_type_const_id;
|
||||
// The location of the impl being looked at for the stack entry.
|
||||
SemIR::InstId impl_loc = SemIR::InstId::None;
|
||||
// TODO: If we make a proper stack class, we only need one `diagnosed_cycle`
|
||||
// field, which resets to false when the stack becomes empty.
|
||||
bool diagnosed_cycle = false;
|
||||
};
|
||||
auto impl_lookup_stack() -> llvm::SmallVector<ImplLookupStackEntry>& {
|
||||
return impl_lookup_stack_;
|
||||
@@ -229,7 +233,7 @@ class Context {
|
||||
// A map from a (self, interface) pair to a final witness.
|
||||
using ImplLookupCacheKey =
|
||||
std::pair<SemIR::ConstantId, SemIR::SpecificInterfaceId>;
|
||||
using ImplLookupCacheMap = Map<ImplLookupCacheKey, SemIR::InstId>;
|
||||
using ImplLookupCacheMap = Map<ImplLookupCacheKey, SemIR::ConstantId>;
|
||||
auto impl_lookup_cache() -> ImplLookupCacheMap& { return impl_lookup_cache_; }
|
||||
|
||||
// An impl lookup query that resulted in a concrete witness from finding an
|
||||
@@ -240,8 +244,8 @@ class Context {
|
||||
SemIR::LocId loc_id;
|
||||
// The query for a witness of an impl for an interface.
|
||||
SemIR::LookupImplWitness query;
|
||||
// The resulting ImplWitness.
|
||||
SemIR::InstId impl_witness;
|
||||
// The resulting final witness.
|
||||
SemIR::ConstantId witness_id;
|
||||
};
|
||||
auto poisoned_concrete_impl_lookup_queries()
|
||||
-> llvm::SmallVector<PoisonedConcreteImplLookupQuery>& {
|
||||
@@ -374,6 +378,7 @@ class Context {
|
||||
return sem_ir().inst_blocks();
|
||||
}
|
||||
auto constants() -> SemIR::ConstantStore& { return sem_ir().constants(); }
|
||||
auto total_ir_count() const -> int { return total_ir_count_; }
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// End of SemIR::File members.
|
||||
@@ -425,7 +430,7 @@ class Context {
|
||||
|
||||
// The stack of RequireImpls for in-progress Interface and Constraint
|
||||
// definitions.
|
||||
ArrayStack<SemIR::RequireImplsId> require_impls_stack_;
|
||||
RequireImplsStack require_impls_stack_;
|
||||
|
||||
// The stack used for qualified declaration name construction.
|
||||
DeclNameStack decl_name_stack_;
|
||||
|
||||
@@ -103,8 +103,9 @@ auto SetBlockArgResultBeforeConstantUse(Context& context,
|
||||
auto cond_const_id = context.constant_values().Get(cond_id);
|
||||
if (!cond_const_id.is_concrete()) {
|
||||
// Symbolic or non-constant condition means a non-constant result.
|
||||
} else if (auto literal = context.insts().TryGetAs<SemIR::BoolLiteral>(
|
||||
context.constant_values().GetInstId(cond_const_id))) {
|
||||
} else if (auto literal =
|
||||
context.constant_values().TryGetInstAs<SemIR::BoolLiteral>(
|
||||
cond_const_id)) {
|
||||
const_id = context.constant_values().Get(
|
||||
literal.value().value.ToBool() ? if_true : if_false);
|
||||
} else {
|
||||
|
||||
+62
-104
@@ -264,7 +264,7 @@ static auto ConvertTupleToArray(Context& context, SemIR::TupleType tuple_type,
|
||||
|
||||
// Check that the tuple is the right size.
|
||||
std::optional<uint64_t> array_bound =
|
||||
sem_ir.GetArrayBoundValue(array_type.bound_id);
|
||||
sem_ir.GetZExtIntValue(array_type.bound_id);
|
||||
if (!array_bound) {
|
||||
// TODO: Should this fall back to using `ImplicitAs`?
|
||||
if (target.diagnose) {
|
||||
@@ -434,10 +434,9 @@ static auto ConvertTupleToType(Context& context, SemIR::LocId loc_id,
|
||||
|
||||
llvm::SmallVector<SemIR::InstId> type_inst_ids;
|
||||
|
||||
auto value_const_inst_id =
|
||||
context.constant_values().GetInstId(value_const_id);
|
||||
if (auto tuple_value =
|
||||
context.insts().TryGetAs<SemIR::TupleValue>(value_const_inst_id)) {
|
||||
context.constant_values().TryGetInstAs<SemIR::TupleValue>(
|
||||
value_const_id)) {
|
||||
for (auto tuple_inst_id :
|
||||
context.inst_blocks().Get(tuple_value->elements_id)) {
|
||||
// TODO: This call recurses back into conversion. Switch to an
|
||||
@@ -448,8 +447,8 @@ static auto ConvertTupleToType(Context& context, SemIR::LocId loc_id,
|
||||
} else {
|
||||
// A value of type TupleType that isn't a TupleValue must be a symbolic
|
||||
// binding.
|
||||
CARBON_CHECK(
|
||||
context.insts().Is<SemIR::SymbolicBinding>(value_const_inst_id));
|
||||
CARBON_CHECK(context.constant_values().InstIs<SemIR::SymbolicBinding>(
|
||||
value_const_id));
|
||||
// Form a TupleAccess for each element in the symbolic value, which is then
|
||||
// converted to a `type` or diagnosed as an error.
|
||||
auto tuple_type = context.types().GetAs<SemIR::TupleType>(value_type_id);
|
||||
@@ -498,9 +497,9 @@ static auto CreateVtablePtrRef(Context& context, SemIR::LocId loc_id,
|
||||
}
|
||||
|
||||
// Returns whether the given expression performs in-place initialization (or is
|
||||
// invalid).
|
||||
static auto IsInPlaceInitializing(Context& context, SemIR::InstId result_id) {
|
||||
auto category = SemIR::GetExprCategory(context.sem_ir(), result_id);
|
||||
// invalid). The category can be passed if known, otherwise it will be computed.
|
||||
static auto IsInPlaceInitializing(Context& context, SemIR::InstId result_id,
|
||||
SemIR::ExprCategory category) {
|
||||
return category == SemIR::ExprCategory::InPlaceInitializing ||
|
||||
(category == SemIR::ExprCategory::ReprInitializing &&
|
||||
SemIR::InitRepr::ForType(context.sem_ir(),
|
||||
@@ -508,6 +507,10 @@ static auto IsInPlaceInitializing(Context& context, SemIR::InstId result_id) {
|
||||
.kind == SemIR::InitRepr::InPlace) ||
|
||||
category == SemIR::ExprCategory::Error;
|
||||
}
|
||||
static auto IsInPlaceInitializing(Context& context, SemIR::InstId result_id) {
|
||||
auto category = SemIR::GetExprCategory(context.sem_ir(), result_id);
|
||||
return IsInPlaceInitializing(context, result_id, category);
|
||||
}
|
||||
|
||||
// Returns the index of the vptr field in the given struct type fields, or
|
||||
// None if there is no vptr field.
|
||||
@@ -832,6 +835,8 @@ static auto ConvertStructToClass(Context& context, SemIR::StructType src_type,
|
||||
SemIR::InstId value_id,
|
||||
ConversionTarget target,
|
||||
bool is_partial = false) -> SemIR::InstId {
|
||||
CARBON_CHECK(target.kind != ConversionTarget::InPlaceInitializing ||
|
||||
target.storage_id.has_value());
|
||||
PendingBlock target_block(&context);
|
||||
auto& dest_class_info = context.classes().Get(dest_type.class_id);
|
||||
CARBON_CHECK(is_partial ||
|
||||
@@ -1512,7 +1517,7 @@ static auto PerformBuiltinConversion(Context& context, SemIR::LocId loc_id,
|
||||
// by finding impl witnesses for the target FacetType.
|
||||
auto lookup_result = LookupImplWitness(
|
||||
context, loc_id, sem_ir.constant_values().Get(type_inst_id),
|
||||
sem_ir.types().GetConstantId(target.type_id));
|
||||
sem_ir.types().GetConstantId(target.type_id), target.diagnose);
|
||||
if (lookup_result.has_value()) {
|
||||
if (lookup_result.has_error_value()) {
|
||||
return SemIR::ErrorInst::InstId;
|
||||
@@ -1545,32 +1550,10 @@ static auto PerformBuiltinConversion(Context& context, SemIR::LocId loc_id,
|
||||
return value_id;
|
||||
}
|
||||
|
||||
// Determine whether this is a C++ enum type.
|
||||
// TODO: This should be removed once we can properly add a `Copy` impl for C++
|
||||
// enum types.
|
||||
static auto IsCppEnum(Context& context, SemIR::TypeId type_id) -> bool {
|
||||
auto class_type = context.types().TryGetAs<SemIR::ClassType>(type_id);
|
||||
if (!class_type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// A C++-imported class type that is an adapter is an enum.
|
||||
auto& class_info = context.classes().Get(class_type->class_id);
|
||||
return class_info.adapt_id.has_value() &&
|
||||
context.name_scopes().Get(class_info.scope_id).is_cpp_scope();
|
||||
}
|
||||
|
||||
// Given a value expression, form a corresponding initializer that copies from
|
||||
// that value to the specified target, if it is possible to do so.
|
||||
static auto PerformCopy(Context& context, SemIR::InstId expr_id,
|
||||
const ConversionTarget& target) -> SemIR::InstId {
|
||||
// TODO: We don't have a mechanism yet to generate `Copy` impls for each enum
|
||||
// type imported from C++. For now we fake it by providing a direct copy.
|
||||
auto type_id = context.insts().Get(expr_id).type_id();
|
||||
if (IsCppEnum(context, type_id)) {
|
||||
return expr_id;
|
||||
}
|
||||
|
||||
auto copy_id = BuildUnaryOperator(
|
||||
context, SemIR::LocId(expr_id), {.interface_name = CoreIdentifier::Copy},
|
||||
expr_id, target.diagnose, [&](auto& builder) {
|
||||
@@ -1581,9 +1564,10 @@ static auto PerformCopy(Context& context, SemIR::InstId expr_id,
|
||||
return copy_id;
|
||||
}
|
||||
|
||||
// Convert a value expression so that it can be used to initialize a C++ thunk
|
||||
// parameter.
|
||||
static auto ConvertValueForCppThunkRef(Context& context, SemIR::InstId expr_id)
|
||||
// Tries to form a `ValueAsRef` conversion that extracts the pointer value from
|
||||
// a value expression with a pointer value representation. Returns the converted
|
||||
// expression, or None if the conversion was not applicable.
|
||||
static auto TryMakeValueAsRef(Context& context, SemIR::InstId expr_id)
|
||||
-> SemIR::InstId {
|
||||
auto expr = context.insts().Get(expr_id);
|
||||
|
||||
@@ -1596,15 +1580,7 @@ static auto ConvertValueForCppThunkRef(Context& context, SemIR::InstId expr_id)
|
||||
{.type_id = expr.type_id(), .value_id = expr_id});
|
||||
}
|
||||
|
||||
// Otherwise, we need a temporary to pass as the thunk argument. Create a copy
|
||||
// and initialize a temporary from it.
|
||||
auto temporary_id = AddInst<SemIR::TemporaryStorage>(
|
||||
context, SemIR::LocId(expr_id), {.type_id = expr.type_id()});
|
||||
expr_id = Initialize(context, SemIR::LocId(expr_id), temporary_id, expr_id);
|
||||
return AddInstWithCleanup<SemIR::Temporary>(context, SemIR::LocId(expr_id),
|
||||
{.type_id = expr.type_id(),
|
||||
.storage_id = temporary_id,
|
||||
.init_id = expr_id});
|
||||
return SemIR::InstId::None;
|
||||
}
|
||||
|
||||
// Returns the Core interface name to use for a given kind of conversion.
|
||||
@@ -1696,10 +1672,7 @@ class CategoryConverter {
|
||||
auto CategoryConverter::DoStep(const SemIR::InstId expr_id,
|
||||
const SemIR::ExprCategory category) const
|
||||
-> State {
|
||||
CARBON_DCHECK(SemIR::GetExprCategory(sem_ir_, expr_id) == category ||
|
||||
// TODO: Drop this special case once PerformCopy on C++ enums
|
||||
// produces an initializing expression.
|
||||
IsCppEnum(context_, target_.type_id));
|
||||
CARBON_DCHECK(SemIR::GetExprCategory(sem_ir_, expr_id) == category);
|
||||
switch (category) {
|
||||
case SemIR::ExprCategory::NotExpr:
|
||||
case SemIR::ExprCategory::Mixed:
|
||||
@@ -1722,19 +1695,15 @@ auto CategoryConverter::DoStep(const SemIR::InstId expr_id,
|
||||
// hasn't already been set. However, we skip this if the type is a C++
|
||||
// enum: in that case, we don't actually have an initializing
|
||||
// expression, we're just pretending we do.
|
||||
auto new_storage_id = target_.storage_id;
|
||||
if (!IsCppEnum(context_, target_.type_id)) {
|
||||
new_storage_id =
|
||||
OverwriteTemporaryStorageArg(sem_ir_, expr_id, target_);
|
||||
}
|
||||
auto new_storage_id =
|
||||
OverwriteTemporaryStorageArg(sem_ir_, expr_id, target_);
|
||||
|
||||
// If in-place initialization was requested, and it hasn't already
|
||||
// happened, ensure it happens now.
|
||||
if (target_.kind == ConversionTarget::InPlaceInitializing &&
|
||||
category != SemIR::ExprCategory::InPlaceInitializing &&
|
||||
SemIR::InitRepr::ForType(sem_ir_, target_.type_id)
|
||||
.MightBeByCopy()) {
|
||||
!IsInPlaceInitializing(context_, expr_id, category)) {
|
||||
target_.storage_access_block->InsertHere();
|
||||
CARBON_CHECK(new_storage_id.has_value());
|
||||
return Done{AddInst<SemIR::InPlaceInit>(context_, loc_id_,
|
||||
{.type_id = target_.type_id,
|
||||
.src_id = expr_id,
|
||||
@@ -1827,6 +1796,7 @@ auto CategoryConverter::DoStep(const SemIR::InstId expr_id,
|
||||
}
|
||||
return Done{SemIR::ErrorInst::InstId};
|
||||
}
|
||||
|
||||
if (target_.kind == ConversionTarget::RefParam ||
|
||||
target_.kind == ConversionTarget::UnmarkedRefParam) {
|
||||
if (target_.diagnose) {
|
||||
@@ -1837,39 +1807,25 @@ auto CategoryConverter::DoStep(const SemIR::InstId expr_id,
|
||||
return Done{SemIR::ErrorInst::InstId};
|
||||
}
|
||||
|
||||
// When initializing a C++ thunk parameter, try to pass a value "by
|
||||
// reference".
|
||||
if (target_.kind == ConversionTarget::CppThunkRef) {
|
||||
if (auto result_id = TryMakeValueAsRef(context_, expr_id);
|
||||
result_id.has_value()) {
|
||||
return Done{result_id};
|
||||
}
|
||||
// Otherwise, fall through to make a copy.
|
||||
}
|
||||
|
||||
// When initializing from a value, perform a copy.
|
||||
if (target_.is_initializer()) {
|
||||
if (target_.is_initializer() ||
|
||||
target_.kind == ConversionTarget::CppThunkRef) {
|
||||
auto copy_id = PerformCopy(context_, expr_id, target_);
|
||||
if (copy_id == SemIR::ErrorInst::InstId) {
|
||||
return Done{SemIR::ErrorInst::InstId};
|
||||
}
|
||||
// Deal with special-case category behavior of PerformCopy.
|
||||
switch (SemIR::GetExprCategory(sem_ir_, copy_id)) {
|
||||
case SemIR::ExprCategory::Value:
|
||||
// As a temporary workaround, PerformCopy on a C++ enum currently
|
||||
// returns the unchanged value, but we treat it as an initializing
|
||||
// expression.
|
||||
// TODO: Drop this case once it's no longer applicable.
|
||||
CARBON_CHECK(IsCppEnum(context_, target_.type_id));
|
||||
[[fallthrough]];
|
||||
case SemIR::ExprCategory::ReprInitializing:
|
||||
// The common case: PerformCopy produces an initializing expression.
|
||||
return NextStep{.expr_id = copy_id,
|
||||
.category = SemIR::ExprCategory::ReprInitializing};
|
||||
case SemIR::ExprCategory::InPlaceInitializing:
|
||||
// A C++ copy operation produces an ephemeral entire reference.
|
||||
return NextStep{
|
||||
.expr_id = copy_id,
|
||||
.category = SemIR::ExprCategory::InPlaceInitializing};
|
||||
default:
|
||||
CARBON_FATAL("Unexpected category of copy operation {0}", category);
|
||||
}
|
||||
}
|
||||
|
||||
// When initializing a C++ thunk parameter, form a reference, creating a
|
||||
// temporary if needed.
|
||||
if (target_.kind == ConversionTarget::CppThunkRef) {
|
||||
return Done{ConvertValueForCppThunkRef(context_, expr_id)};
|
||||
return NextStep{.expr_id = copy_id,
|
||||
.category = SemIR::GetExprCategory(sem_ir_, copy_id)};
|
||||
}
|
||||
|
||||
return Done{expr_id};
|
||||
@@ -1986,8 +1942,9 @@ auto Convert(Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id,
|
||||
// Clear storage_id in cases where it's clearly meaningless, to avoid misuse
|
||||
// and simplify the resulting SemIR.
|
||||
if (!target.is_initializer() ||
|
||||
SemIR::InitRepr::ForType(context.sem_ir(), target.type_id).kind ==
|
||||
SemIR::InitRepr::None) {
|
||||
(target.kind == ConversionTarget::Initializing &&
|
||||
SemIR::InitRepr::ForType(context.sem_ir(), target.type_id).kind ==
|
||||
SemIR::InitRepr::None)) {
|
||||
target.storage_id = SemIR::InstId::None;
|
||||
}
|
||||
|
||||
@@ -2014,17 +1971,18 @@ auto Convert(Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id,
|
||||
// didn't apply in the template definition.
|
||||
// TODO: Support this for targets other than `Value`.
|
||||
if (sem_ir.insts().Get(expr_id).type_id() != target.type_id &&
|
||||
target.kind == ConversionTarget::Value &&
|
||||
(OperandIsDependent(context, expr_id) ||
|
||||
OperandIsDependent(context, target.type_id))) {
|
||||
target.kind == ConversionTarget::Value) {
|
||||
auto target_type_inst_id = context.types().GetTypeInstId(target.type_id);
|
||||
return AddDependentActionSplice(
|
||||
context, loc_id,
|
||||
SemIR::ConvertToValueAction{
|
||||
.type_id = GetSingletonType(context, SemIR::InstType::TypeInstId),
|
||||
.inst_id = expr_id,
|
||||
.target_type_inst_id = target_type_inst_id},
|
||||
target_type_inst_id);
|
||||
SemIR::ConvertToValueAction convert_action = {
|
||||
.type_id = GetSingletonType(context, SemIR::InstType::TypeInstId),
|
||||
.inst_id = expr_id,
|
||||
.target_type_inst_id = target_type_inst_id};
|
||||
// We don't use `HandleAction` here because it would call `PerformAction`
|
||||
// inline if it's performable, which would lead to infinite recursion.
|
||||
if (!ActionIsPerformable(context, convert_action)) {
|
||||
return AddDependentActionSplice(context, loc_id, convert_action,
|
||||
target_type_inst_id);
|
||||
}
|
||||
}
|
||||
|
||||
// If this is not a builtin conversion, try an `ImplicitAs` conversion.
|
||||
@@ -2276,8 +2234,8 @@ auto ReturnExprAsForm(Context& context, SemIR::LocId loc_id,
|
||||
}
|
||||
form_inst_id = AddInst(
|
||||
context,
|
||||
SemIR::LocIdAndInst::UncheckedLoc(
|
||||
loc_id,
|
||||
SemIR::LocIdAndInst::RuntimeVerified(
|
||||
context.sem_ir(), loc_id,
|
||||
SemIR::RefForm{.type_id = SemIR::FormType::TypeId,
|
||||
.type_component_inst_id =
|
||||
context.types().GetAsTypeInstId(type_inst_id)}));
|
||||
@@ -2292,12 +2250,12 @@ auto ReturnExprAsForm(Context& context, SemIR::LocId loc_id,
|
||||
return Context::FormExpr::Error;
|
||||
}
|
||||
form_inst_id = AddInst(
|
||||
context,
|
||||
SemIR::LocIdAndInst::UncheckedLoc(
|
||||
loc_id, SemIR::InitForm{
|
||||
.type_id = SemIR::FormType::TypeId,
|
||||
.type_component_inst_id =
|
||||
context.types().GetAsTypeInstId(type_inst_id)}));
|
||||
context, SemIR::LocIdAndInst::RuntimeVerified(
|
||||
context.sem_ir(), loc_id,
|
||||
SemIR::InitForm{
|
||||
.type_id = SemIR::FormType::TypeId,
|
||||
.type_component_inst_id =
|
||||
context.types().GetAsTypeInstId(type_inst_id)}));
|
||||
}
|
||||
|
||||
auto type_const_id = context.constant_values().Get(type_inst_id);
|
||||
|
||||
@@ -35,6 +35,7 @@ CARBON_CORE_IDENTIFIER(Copy)
|
||||
CARBON_CORE_IDENTIFIER(CppCompat)
|
||||
CARBON_CORE_IDENTIFIER(CppUnsafeDeref)
|
||||
CARBON_CORE_IDENTIFIER(Dec)
|
||||
CARBON_CORE_IDENTIFIER(Default)
|
||||
CARBON_CORE_IDENTIFIER(DefaultOrUnformed)
|
||||
CARBON_CORE_IDENTIFIER(Destroy)
|
||||
CARBON_CORE_IDENTIFIER(DivAssignWith)
|
||||
|
||||
@@ -44,4 +44,15 @@ auto MapCppAccess(clang::DeclAccessPair access_pair) -> SemIR::AccessKind {
|
||||
}
|
||||
}
|
||||
|
||||
auto MapToCppAccess(SemIR::AccessKind access) -> clang::AccessSpecifier {
|
||||
switch (access) {
|
||||
case SemIR::AccessKind::Public:
|
||||
return clang::AS_public;
|
||||
case SemIR::AccessKind::Protected:
|
||||
return clang::AS_protected;
|
||||
case SemIR::AccessKind::Private:
|
||||
return clang::AS_private;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Carbon::Check
|
||||
|
||||
@@ -13,6 +13,10 @@ namespace Carbon::Check {
|
||||
// access) pair.
|
||||
auto MapCppAccess(clang::DeclAccessPair access_pair) -> SemIR::AccessKind;
|
||||
|
||||
// Maps a Carbon access kind to a C++ access specifier, suitable for use when
|
||||
// declaring a C++ class member with the same access. Never returns AS_none.
|
||||
auto MapToCppAccess(SemIR::AccessKind access) -> clang::AccessSpecifier;
|
||||
|
||||
} // namespace Carbon::Check
|
||||
|
||||
#endif // CARBON_TOOLCHAIN_CHECK_CPP_ACCESS_H_
|
||||
|
||||
@@ -223,7 +223,8 @@ static auto ConvertArgToTemplateArg(
|
||||
}
|
||||
|
||||
// TODO: provide a better location.
|
||||
auto template_loc = clang::TemplateArgumentLocInfo();
|
||||
auto template_loc = clang::TemplateArgumentLocInfo(context.ast_context(),
|
||||
clang::SourceLocation());
|
||||
|
||||
auto const_inst_id =
|
||||
context.constant_values().GetConstantInstId(converted_inst_id);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "toolchain/check/cpp/constant.h"
|
||||
|
||||
#include "toolchain/check/cpp/import.h"
|
||||
#include "toolchain/check/cpp/location.h"
|
||||
#include "toolchain/check/cpp/type_mapping.h"
|
||||
#include "toolchain/check/eval.h"
|
||||
#include "toolchain/check/member_access.h"
|
||||
@@ -195,6 +196,11 @@ auto MapConstantToAPValue(Context& context, SemIR::InstId const_inst_id,
|
||||
|
||||
static auto ConvertArgToExpr(Context& context, SemIR::InstId arg_inst_id,
|
||||
clang::QualType param_type) -> clang::Expr* {
|
||||
if (auto temporary =
|
||||
context.insts().TryGetAs<SemIR::Temporary>(arg_inst_id)) {
|
||||
arg_inst_id = temporary->init_id;
|
||||
}
|
||||
|
||||
auto const_inst_id = context.constant_values().GetConstantInstId(arg_inst_id);
|
||||
if (!const_inst_id.has_value()) {
|
||||
return nullptr;
|
||||
@@ -226,7 +232,7 @@ auto EvalCppCall(Context& context, SemIR::LocId loc_id,
|
||||
context.ast_context(), /*QualifierLoc=*/clang::NestedNameSpecifierLoc(),
|
||||
/*TemplateKWLoc=*/clang::SourceLocation(), function_decl,
|
||||
/*RefersToEnclosingVariableOrCapture=*/false,
|
||||
/*NameLoc=*/clang::SourceLocation(), function_decl->getType(),
|
||||
/*NameLoc=*/GetCppLocation(context, loc_id), function_decl->getType(),
|
||||
clang::VK_LValue);
|
||||
|
||||
// Cast to a function pointer type.
|
||||
@@ -253,18 +259,23 @@ auto EvalCppCall(Context& context, SemIR::LocId loc_id,
|
||||
auto* call_expr = clang::CallExpr::Create(
|
||||
context.ast_context(), implicit_cast_expr, arg_exprs,
|
||||
function_decl->getCallResultType(), clang::VK_PRValue,
|
||||
/*RParenLoc=*/clang::SourceLocation(), clang::FPOptionsOverride());
|
||||
/*RParenLoc=*/GetCppLocation(context, loc_id),
|
||||
clang::FPOptionsOverride());
|
||||
|
||||
// Evaluate the expr as a constant and map that to Carbon constant.
|
||||
clang::SmallVector<clang::PartialDiagnosticAt> notes;
|
||||
clang::Expr::EvalResult eval_result;
|
||||
eval_result.Diag = ¬es;
|
||||
if (!call_expr->EvaluateAsConstantExpr(eval_result, context.ast_context())) {
|
||||
// TODO: improve this diagnostic with information from `eval_result`.
|
||||
CARBON_DIAGNOSTIC(CppConstexprEval, Error,
|
||||
"failed to evaluate {0:consteval|constexpr} function "
|
||||
"call as a constant",
|
||||
Diagnostics::BoolAsSelect);
|
||||
context.emitter().Emit(loc_id, CppConstexprEval,
|
||||
function_decl->isConsteval());
|
||||
if (!function_decl->isConsteval()) {
|
||||
return SemIR::ConstantId::NotConstant;
|
||||
}
|
||||
context.clang_sema().Diag(call_expr->getBeginLoc(),
|
||||
clang::diag::err_invalid_consteval_call)
|
||||
<< function_decl << /*is consteval*/ true;
|
||||
for (const auto& note : notes) {
|
||||
context.clang_sema().Diag(note.first, note.second);
|
||||
}
|
||||
return SemIR::ErrorInst::ConstantId;
|
||||
}
|
||||
|
||||
@@ -272,4 +283,67 @@ auto EvalCppCall(Context& context, SemIR::LocId loc_id,
|
||||
function_decl->getCallResultType());
|
||||
}
|
||||
|
||||
auto MaybeModifyCppThunkCallForConstEval(Context& context, SemIR::Call* call)
|
||||
-> void {
|
||||
clang::FunctionDecl* function_decl = nullptr;
|
||||
SemIR::InstId thunk_callee_inst_id = SemIR::InstId::None;
|
||||
|
||||
// Check if the callee is a C++ thunk for a constexpr function. If so,
|
||||
// fill in `function_decl` and `thunk_callee_inst_id`.
|
||||
auto callee = SemIR::GetCallee(context.sem_ir(), call->callee_id);
|
||||
if (auto* callee_function = std::get_if<SemIR::CalleeFunction>(&callee)) {
|
||||
auto function = context.functions().Get(callee_function->function_id);
|
||||
|
||||
thunk_callee_inst_id = function.cpp_thunk_callee();
|
||||
if (!thunk_callee_inst_id.has_value()) {
|
||||
return;
|
||||
}
|
||||
auto thunk_callee_function = context.functions().Get(
|
||||
context.insts()
|
||||
.GetAs<SemIR::FunctionDecl>(thunk_callee_inst_id)
|
||||
.function_id);
|
||||
|
||||
function_decl =
|
||||
cast<clang::FunctionDecl>(context.clang_decls()
|
||||
.Get(thunk_callee_function.clang_decl_id)
|
||||
.GetAsKey()
|
||||
.decl);
|
||||
|
||||
if (!(function_decl->isConstexpr() || function_decl->isConsteval())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (function_decl->isDefaulted()) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
auto thunk_args = context.inst_blocks().Get(call->args_id);
|
||||
|
||||
// Get the new call arguments. This drops the return slot arg, if
|
||||
// present. It also remaps arguments that are a pointer in the thunk,
|
||||
// but a non-pointer in the callee.
|
||||
llvm::SmallVector<SemIR::InstId> new_args;
|
||||
for (auto [arg_inst_id, parm_var_decl] :
|
||||
llvm::zip(thunk_args, function_decl->parameters())) {
|
||||
auto parm_type = parm_var_decl->getType();
|
||||
auto new_arg_inst_id = arg_inst_id;
|
||||
|
||||
// TODO: reuse the logic in `check/cpp/thunk.cpp` to determine
|
||||
// whether to dereference the argument.
|
||||
if (!parm_type->isPointerType()) {
|
||||
if (auto addr_of = context.insts().TryGetAs<SemIR::AddrOf>(arg_inst_id)) {
|
||||
new_arg_inst_id = addr_of->lvalue_id;
|
||||
}
|
||||
}
|
||||
|
||||
new_args.push_back(new_arg_inst_id);
|
||||
}
|
||||
|
||||
call->callee_id = thunk_callee_inst_id;
|
||||
call->args_id = context.inst_blocks().AddCanonical(new_args);
|
||||
}
|
||||
|
||||
} // namespace Carbon::Check
|
||||
|
||||
@@ -34,6 +34,12 @@ auto EvalCppCall(Context& context, SemIR::LocId loc_id,
|
||||
SemIR::ClangDeclId clang_decl_id, SemIR::InstBlockId args_id)
|
||||
-> SemIR::ConstantId;
|
||||
|
||||
// If the callee is a C++ thunk, modify `call` to directly call the
|
||||
// callee of the C++ thunk. Otherwise, does nothing and leaves `call`
|
||||
// unmodified.
|
||||
auto MaybeModifyCppThunkCallForConstEval(Context& context, SemIR::Call* call)
|
||||
-> void;
|
||||
|
||||
} // namespace Carbon::Check
|
||||
|
||||
#endif // CARBON_TOOLCHAIN_CHECK_CPP_CONSTANT_H_
|
||||
|
||||
@@ -0,0 +1,514 @@
|
||||
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
||||
// Exceptions. See /LICENSE for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
|
||||
#include "toolchain/check/cpp/export.h"
|
||||
|
||||
#include "toolchain/check/cpp/import.h"
|
||||
#include "toolchain/check/cpp/location.h"
|
||||
#include "toolchain/check/cpp/type_mapping.h"
|
||||
#include "toolchain/check/function.h"
|
||||
#include "toolchain/check/thunk.h"
|
||||
#include "toolchain/check/type.h"
|
||||
#include "toolchain/sem_ir/mangler.h"
|
||||
|
||||
namespace Carbon::Check {
|
||||
|
||||
// If the given name scope was produced by importing a C++ declaration or has
|
||||
// already been exported to C++, return the corresponding Clang decl context.
|
||||
static auto GetClangDeclContextForScope(Context& context,
|
||||
SemIR::NameScopeId scope_id)
|
||||
-> clang::DeclContext* {
|
||||
if (!scope_id.has_value()) {
|
||||
return nullptr;
|
||||
}
|
||||
auto& scope = context.name_scopes().Get(scope_id);
|
||||
auto clang_decl_context_id = scope.clang_decl_context_id();
|
||||
if (!clang_decl_context_id.has_value()) {
|
||||
return nullptr;
|
||||
}
|
||||
auto* decl = context.clang_decls().Get(clang_decl_context_id).key.decl;
|
||||
return cast<clang::DeclContext>(decl);
|
||||
}
|
||||
|
||||
auto ExportNameScopeToCpp(Context& context, SemIR::LocId loc_id,
|
||||
SemIR::NameScopeId name_scope_id)
|
||||
-> clang::DeclContext* {
|
||||
llvm::SmallVector<SemIR::NameScopeId> name_scope_ids_to_create;
|
||||
|
||||
// Walk through the parent scopes, looking for one that's already mapped into
|
||||
// C++. We already mapped the package scope to ::Carbon, so we must find one.
|
||||
clang::DeclContext* decl_context = nullptr;
|
||||
while (true) {
|
||||
// If this name scope was produced by importing a C++ declaration or has
|
||||
// already been exported to C++, return the corresponding Clang declaration.
|
||||
if (auto* existing_decl_context =
|
||||
GetClangDeclContextForScope(context, name_scope_id)) {
|
||||
decl_context = existing_decl_context;
|
||||
break;
|
||||
}
|
||||
|
||||
// Otherwise, continue to the parent and create a scope for it first.
|
||||
name_scope_ids_to_create.push_back(name_scope_id);
|
||||
name_scope_id = context.name_scopes().Get(name_scope_id).parent_scope_id();
|
||||
|
||||
// TODO: What should happen if there's an intervening function scope?
|
||||
CARBON_CHECK(
|
||||
name_scope_id.has_value(),
|
||||
"Reached the top level without finding a scope mapped into C++");
|
||||
}
|
||||
|
||||
// Create the name scopes in order, starting from the outermost one.
|
||||
while (!name_scope_ids_to_create.empty()) {
|
||||
name_scope_id = name_scope_ids_to_create.pop_back_val();
|
||||
|
||||
auto& name_scope = context.name_scopes().Get(name_scope_id);
|
||||
auto* identifier_info =
|
||||
GetClangIdentifierInfo(context, name_scope.name_id());
|
||||
if (!identifier_info) {
|
||||
// TODO: Handle keyword package names like `Cpp` and `Core`. These can
|
||||
// be named from C++ via an alias.
|
||||
context.TODO(loc_id, "interop with non-identifier package name");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto inst = context.insts().Get(name_scope.inst_id());
|
||||
if (inst.Is<SemIR::Namespace>()) {
|
||||
// TODO: Provide a source location.
|
||||
auto* namespace_decl = clang::NamespaceDecl::Create(
|
||||
context.ast_context(), decl_context, false, clang::SourceLocation(),
|
||||
clang::SourceLocation(), identifier_info, nullptr, false);
|
||||
decl_context->addHiddenDecl(namespace_decl);
|
||||
decl_context = namespace_decl;
|
||||
} else if (inst.Is<SemIR::ClassDecl>()) {
|
||||
// TODO: Provide a source location.
|
||||
auto* record_decl = clang::CXXRecordDecl::Create(
|
||||
context.ast_context(), clang::TagTypeKind::Class, decl_context,
|
||||
clang::SourceLocation(), clang::SourceLocation(), identifier_info);
|
||||
// If this is a member class, set its access.
|
||||
if (isa<clang::CXXRecordDecl>(decl_context)) {
|
||||
// TODO: Map Carbon access to C++ access.
|
||||
record_decl->setAccess(clang::AS_public);
|
||||
}
|
||||
|
||||
decl_context->addHiddenDecl(record_decl);
|
||||
decl_context = record_decl;
|
||||
decl_context->setHasExternalLexicalStorage();
|
||||
} else {
|
||||
context.TODO(loc_id, "non-class non-namespace name scope");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
decl_context->setHasExternalVisibleStorage();
|
||||
|
||||
auto key = SemIR::ClangDeclKey::ForNonFunctionDecl(
|
||||
cast<clang::Decl>(decl_context));
|
||||
auto clang_decl_id = context.clang_decls().Add(
|
||||
{.key = key, .inst_id = name_scope.inst_id()});
|
||||
name_scope.set_clang_decl_context_id(clang_decl_id, /*is_cpp_scope=*/false);
|
||||
}
|
||||
|
||||
return decl_context;
|
||||
}
|
||||
|
||||
auto ExportClassToCpp(Context& context, SemIR::LocId loc_id,
|
||||
SemIR::InstId class_inst_id, SemIR::ClassType class_type)
|
||||
-> clang::TagDecl* {
|
||||
// TODO: A lot of logic in this function is shared with ExportNameScopeToCpp.
|
||||
// This should be refactored.
|
||||
|
||||
if (class_type.specific_id.has_value()) {
|
||||
context.TODO(loc_id, "interop with specific class");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto& class_info = context.classes().Get(class_type.class_id);
|
||||
|
||||
// If this class was produced by importing a C++ declaration or has
|
||||
// already been exported to C++, return the corresponding Clang declaration.
|
||||
// That could either be a CXXRecordDecl or an EnumDecl.
|
||||
if (auto* decl_context =
|
||||
GetClangDeclContextForScope(context, class_info.scope_id)) {
|
||||
return cast<clang::TagDecl>(decl_context);
|
||||
}
|
||||
|
||||
auto* identifier_info = GetClangIdentifierInfo(context, class_info.name_id);
|
||||
CARBON_CHECK(identifier_info, "non-identifier class name {0}",
|
||||
class_info.name_id);
|
||||
|
||||
auto* decl_context =
|
||||
ExportNameScopeToCpp(context, loc_id, class_info.parent_scope_id);
|
||||
// TODO: Provide a source location.
|
||||
auto* record_decl = clang::CXXRecordDecl::Create(
|
||||
context.ast_context(), clang::TagTypeKind::Class, decl_context,
|
||||
clang::SourceLocation(), clang::SourceLocation(), identifier_info);
|
||||
// If this is a member class, set its access.
|
||||
if (isa<clang::CXXRecordDecl>(decl_context)) {
|
||||
// TODO: Map Carbon access to C++ access.
|
||||
record_decl->setAccess(clang::AS_public);
|
||||
}
|
||||
|
||||
record_decl->setHasExternalLexicalStorage();
|
||||
record_decl->setHasExternalVisibleStorage();
|
||||
|
||||
auto key =
|
||||
SemIR::ClangDeclKey::ForNonFunctionDecl(cast<clang::Decl>(record_decl));
|
||||
auto clang_decl_id =
|
||||
context.clang_decls().Add({.key = key, .inst_id = class_inst_id});
|
||||
if (class_info.scope_id.has_value()) {
|
||||
// TODO: Record the Carbon class -> clang declaration mapping for incomplete
|
||||
// classes too.
|
||||
context.name_scopes()
|
||||
.Get(class_info.scope_id)
|
||||
.set_clang_decl_context_id(clang_decl_id, /*is_cpp_scope=*/false);
|
||||
}
|
||||
return record_decl;
|
||||
}
|
||||
|
||||
// Create a `clang::FunctionDecl` for the given Carbon function. This
|
||||
// can be used to call the Carbon function from C++. The Carbon
|
||||
// function's ABI must be compatible with C++.
|
||||
//
|
||||
// The resulting decl is used to allow a generated C++ function to call
|
||||
// a generated Carbon function.
|
||||
static auto BuildCppFunctionDeclForCarbonFn(Context& context,
|
||||
SemIR::LocId loc_id,
|
||||
SemIR::FunctionId function_id)
|
||||
-> clang::FunctionDecl* {
|
||||
auto clang_loc = GetCppLocation(context, loc_id);
|
||||
|
||||
const SemIR::Function& function = context.functions().Get(function_id);
|
||||
|
||||
// Get parameters types.
|
||||
auto carbon_function_params =
|
||||
context.inst_blocks().Get(function.call_param_patterns_id);
|
||||
llvm::SmallVector<clang::QualType> cpp_param_types;
|
||||
for (auto param_inst_id : carbon_function_params) {
|
||||
auto scrutinee_type_id = ExtractScrutineeType(
|
||||
context.sem_ir(), context.insts().Get(param_inst_id).type_id());
|
||||
auto cpp_type = MapToCppType(context, scrutinee_type_id);
|
||||
if (cpp_type.isNull()) {
|
||||
context.TODO(loc_id, "failed to map Carbon type to C++");
|
||||
return nullptr;
|
||||
}
|
||||
auto ref_type = context.ast_context().getLValueReferenceType(cpp_type);
|
||||
cpp_param_types.push_back(ref_type);
|
||||
}
|
||||
|
||||
CARBON_CHECK(function.return_type_inst_id == SemIR::TypeInstId::None);
|
||||
auto cpp_return_type = context.ast_context().VoidTy;
|
||||
|
||||
auto cpp_function_type = context.ast_context().getFunctionType(
|
||||
cpp_return_type, cpp_param_types,
|
||||
clang::FunctionProtoType::ExtProtoInfo());
|
||||
|
||||
auto* identifier_info = GetClangIdentifierInfo(context, function.name_id);
|
||||
CARBON_CHECK(identifier_info, "function with non-identifier name {0}",
|
||||
function.name_id);
|
||||
|
||||
auto* function_decl = clang::FunctionDecl::Create(
|
||||
context.ast_context(), context.ast_context().getTranslationUnitDecl(),
|
||||
/*StartLoc=*/clang_loc, /*NLoc=*/clang_loc, identifier_info,
|
||||
cpp_function_type, /*TInfo=*/nullptr, clang::SC_Extern);
|
||||
|
||||
// Build parameter decls.
|
||||
llvm::SmallVector<clang::ParmVarDecl*> param_var_decls;
|
||||
for (auto [i, type] : llvm::enumerate(cpp_param_types)) {
|
||||
clang::ParmVarDecl* param = clang::ParmVarDecl::Create(
|
||||
context.ast_context(), function_decl, /*StartLoc=*/clang_loc,
|
||||
/*IdLoc=*/clang_loc, /*Id=*/nullptr, type, /*TInfo=*/nullptr,
|
||||
clang::SC_None, /*DefArg=*/nullptr);
|
||||
param_var_decls.push_back(param);
|
||||
}
|
||||
function_decl->setParams(param_var_decls);
|
||||
|
||||
// Mangle the function name and attach it to the `FunctionDecl`.
|
||||
SemIR::Mangler m(context.sem_ir(), context.total_ir_count());
|
||||
std::string mangled_name = m.Mangle(function_id, SemIR::SpecificId::None);
|
||||
function_decl->addAttr(
|
||||
clang::AsmLabelAttr::Create(context.ast_context(), mangled_name));
|
||||
|
||||
return function_decl;
|
||||
}
|
||||
|
||||
// Create the declaration of the C++ thunk.
|
||||
static auto BuildCppToCarbonThunkDecl(
|
||||
Context& context, SemIR::LocId loc_id, clang::DeclContext* decl_context,
|
||||
clang::DeclarationName thunk_name,
|
||||
llvm::ArrayRef<clang::QualType> thunk_param_types,
|
||||
SemIR::TypeId return_type_id) -> clang::FunctionDecl* {
|
||||
clang::ASTContext& ast_context = context.ast_context();
|
||||
|
||||
auto clang_loc = GetCppLocation(context, loc_id);
|
||||
|
||||
// Get the C++ return type (this corresponds to the return type of the
|
||||
// target Carbon function).
|
||||
clang::QualType cpp_return_type = context.ast_context().VoidTy;
|
||||
if (return_type_id != SemIR::TypeId::None) {
|
||||
cpp_return_type = MapToCppType(context, return_type_id);
|
||||
if (cpp_return_type.isNull()) {
|
||||
context.TODO(loc_id, "failed to map Carbon return type to C++ type");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
clang::DeclarationNameInfo name_info(thunk_name, clang_loc);
|
||||
|
||||
auto ext_proto_info = clang::FunctionProtoType::ExtProtoInfo();
|
||||
clang::QualType thunk_function_type = ast_context.getFunctionType(
|
||||
cpp_return_type, thunk_param_types, ext_proto_info);
|
||||
|
||||
auto* tinfo =
|
||||
ast_context.getTrivialTypeSourceInfo(thunk_function_type, clang_loc);
|
||||
|
||||
bool uses_fp_intrin = false;
|
||||
bool inline_specified = true;
|
||||
auto constexpr_kind = clang::ConstexprSpecKind::Unspecified;
|
||||
auto trailing_requires_clause = clang::AssociatedConstraint();
|
||||
|
||||
clang::FunctionDecl* thunk_function_decl = nullptr;
|
||||
if (auto* parent_class = dyn_cast<clang::CXXRecordDecl>(decl_context)) {
|
||||
// TODO: Support non-static methods.
|
||||
thunk_function_decl = clang::CXXMethodDecl::Create(
|
||||
ast_context, parent_class, clang_loc, name_info, thunk_function_type,
|
||||
tinfo, clang::SC_Static, uses_fp_intrin, inline_specified,
|
||||
constexpr_kind, clang_loc, trailing_requires_clause);
|
||||
// TODO: Map Carbon access to C++ access.
|
||||
thunk_function_decl->setAccess(clang::AS_public);
|
||||
} else {
|
||||
thunk_function_decl = clang::FunctionDecl::Create(
|
||||
ast_context, decl_context, clang_loc, name_info, thunk_function_type,
|
||||
tinfo, clang::SC_None, uses_fp_intrin, inline_specified,
|
||||
/*hasWrittenPrototype=*/true, constexpr_kind, trailing_requires_clause);
|
||||
}
|
||||
decl_context->addDecl(thunk_function_decl);
|
||||
|
||||
llvm::SmallVector<clang::ParmVarDecl*> param_var_decls;
|
||||
for (auto [i, type] : llvm::enumerate(thunk_param_types)) {
|
||||
clang::ParmVarDecl* thunk_param = clang::ParmVarDecl::Create(
|
||||
ast_context, thunk_function_decl, /*StartLoc=*/clang_loc,
|
||||
/*IdLoc=*/clang_loc, /*Id=*/nullptr, type,
|
||||
/*TInfo=*/nullptr, clang::SC_None, /*DefArg=*/nullptr);
|
||||
param_var_decls.push_back(thunk_param);
|
||||
}
|
||||
thunk_function_decl->setParams(param_var_decls);
|
||||
|
||||
// Force the thunk to be inlined and discarded.
|
||||
thunk_function_decl->addAttr(
|
||||
clang::AlwaysInlineAttr::CreateImplicit(ast_context));
|
||||
thunk_function_decl->addAttr(
|
||||
clang::InternalLinkageAttr::CreateImplicit(ast_context));
|
||||
|
||||
return thunk_function_decl;
|
||||
}
|
||||
|
||||
// Create the body of a C++ thunk that calls a Carbon thunk. The
|
||||
// arguments are passed by reference to the callee.
|
||||
static auto BuildCppToCarbonThunkBody(clang::Sema& sema,
|
||||
clang::FunctionDecl* function_decl,
|
||||
clang::FunctionDecl* callee_function_decl)
|
||||
-> clang::StmtResult {
|
||||
clang::SourceLocation clang_loc = function_decl->getLocation();
|
||||
|
||||
llvm::SmallVector<clang::Stmt*> stmts;
|
||||
|
||||
// Create return storage if the target function returns non-void.
|
||||
const bool has_return_value = !function_decl->getReturnType()->isVoidType();
|
||||
clang::VarDecl* return_storage_var_decl = nullptr;
|
||||
clang::ExprResult return_storage_expr;
|
||||
if (has_return_value) {
|
||||
auto& return_storage_ident =
|
||||
sema.getASTContext().Idents.get("return_storage");
|
||||
return_storage_var_decl =
|
||||
clang::VarDecl::Create(sema.getASTContext(), function_decl,
|
||||
/*StartLoc=*/clang_loc,
|
||||
/*IdLoc=*/clang_loc, &return_storage_ident,
|
||||
function_decl->getReturnType(),
|
||||
/*TInfo=*/nullptr, clang::SC_None);
|
||||
return_storage_var_decl->setNRVOVariable(true);
|
||||
return_storage_expr = sema.BuildDeclRefExpr(
|
||||
return_storage_var_decl, return_storage_var_decl->getType(),
|
||||
clang::VK_LValue, clang_loc);
|
||||
|
||||
auto decl_group_ref = clang::DeclGroupRef(return_storage_var_decl);
|
||||
auto decl_stmt =
|
||||
sema.ActOnDeclStmt(clang::Sema::DeclGroupPtrTy::make(decl_group_ref),
|
||||
clang_loc, clang_loc);
|
||||
stmts.push_back(decl_stmt.get());
|
||||
}
|
||||
|
||||
clang::ExprResult callee = sema.BuildDeclRefExpr(
|
||||
callee_function_decl, callee_function_decl->getType(), clang::VK_PRValue,
|
||||
clang_loc);
|
||||
|
||||
llvm::SmallVector<clang::Expr*> call_args;
|
||||
for (auto* param : function_decl->parameters()) {
|
||||
clang::Expr* call_arg =
|
||||
sema.BuildDeclRefExpr(param, param->getType().getNonReferenceType(),
|
||||
clang::VK_LValue, clang_loc);
|
||||
call_args.push_back(call_arg);
|
||||
}
|
||||
|
||||
// If the target function returns non-void, the Carbon thunk takes an
|
||||
// extra output parameter referencing the return storage.
|
||||
if (has_return_value) {
|
||||
call_args.push_back(return_storage_expr.get());
|
||||
}
|
||||
|
||||
clang::ExprResult call = sema.BuildCallExpr(nullptr, callee.get(), clang_loc,
|
||||
call_args, clang_loc);
|
||||
CARBON_CHECK(call.isUsable());
|
||||
stmts.push_back(call.get());
|
||||
|
||||
if (has_return_value) {
|
||||
auto* return_stmt = clang::ReturnStmt::Create(
|
||||
sema.getASTContext(), clang_loc, return_storage_expr.get(),
|
||||
return_storage_var_decl);
|
||||
stmts.push_back(return_stmt);
|
||||
}
|
||||
|
||||
return clang::CompoundStmt::Create(sema.getASTContext(), stmts,
|
||||
clang::FPOptionsOverride(), clang_loc,
|
||||
clang_loc);
|
||||
}
|
||||
|
||||
// Create a C++ thunk that calls the Carbon thunk. The C++ thunk's
|
||||
// parameter types are mapped from the parameters of the target function
|
||||
// with `MapToCppType`. (Note that the target function here is the
|
||||
// callee of the Carbon thunk.)
|
||||
static auto BuildCppToCarbonThunk(
|
||||
Context& context, SemIR::LocId loc_id, clang::DeclContext* decl_context,
|
||||
llvm::StringRef base_name, clang::FunctionDecl* carbon_function_decl,
|
||||
llvm::ArrayRef<SemIR::TypeId> callee_param_type_ids,
|
||||
SemIR::TypeId return_type_id) -> clang::FunctionDecl* {
|
||||
// Create the thunk's name.
|
||||
llvm::SmallString<64> thunk_name = base_name;
|
||||
thunk_name += "__cpp_thunk";
|
||||
auto& thunk_ident = context.ast_context().Idents.get(thunk_name);
|
||||
|
||||
llvm::SmallVector<clang::QualType> param_types;
|
||||
for (auto type_id : callee_param_type_ids) {
|
||||
auto cpp_type = MapToCppType(context, type_id);
|
||||
if (cpp_type.isNull()) {
|
||||
context.TODO(loc_id, "failed to map C++ type to Carbon");
|
||||
return nullptr;
|
||||
}
|
||||
param_types.push_back(cpp_type);
|
||||
}
|
||||
|
||||
auto* thunk_function_decl = BuildCppToCarbonThunkDecl(
|
||||
context, loc_id, decl_context, &thunk_ident, param_types, return_type_id);
|
||||
|
||||
// Build the thunk function body.
|
||||
clang::Sema& sema = context.clang_sema();
|
||||
clang::Sema::ContextRAII context_raii(sema, thunk_function_decl);
|
||||
sema.ActOnStartOfFunctionDef(nullptr, thunk_function_decl);
|
||||
clang::StmtResult body = BuildCppToCarbonThunkBody(sema, thunk_function_decl,
|
||||
carbon_function_decl);
|
||||
sema.ActOnFinishFunctionBody(thunk_function_decl, body.get());
|
||||
CARBON_CHECK(!body.isInvalid());
|
||||
|
||||
context.clang_sema().getASTConsumer().HandleTopLevelDecl(
|
||||
clang::DeclGroupRef(thunk_function_decl));
|
||||
return thunk_function_decl;
|
||||
}
|
||||
|
||||
// Create a Carbon thunk that calls `callee`. The thunk's parameters are
|
||||
// all references to the callee parameter type.
|
||||
static auto BuildCarbonToCarbonThunk(
|
||||
Context& context, SemIR::LocId loc_id, SemIR::FunctionId callee_function_id,
|
||||
const SemIR::Function& callee,
|
||||
llvm::ArrayRef<SemIR::TypeId> callee_param_type_ids) -> SemIR::FunctionId {
|
||||
// Create the thunk's name.
|
||||
llvm::SmallString<64> thunk_name =
|
||||
context.names().GetFormatted(callee.name_id);
|
||||
thunk_name += "__carbon_thunk";
|
||||
auto& ident = context.ast_context().Idents.get(thunk_name);
|
||||
auto thunk_name_id =
|
||||
SemIR::NameId::ForIdentifier(context.identifiers().Add(ident.getName()));
|
||||
|
||||
// Get the thunk's parameters. These match the callee parameters, with
|
||||
// the addition of an output parameter for the callee's return value
|
||||
// (if it has one).
|
||||
llvm::SmallVector<SemIR::TypeId> thunk_param_type_ids(callee_param_type_ids);
|
||||
auto callee_return_type_id = callee.GetDeclaredReturnType(context.sem_ir());
|
||||
if (callee_return_type_id != SemIR::TypeId::None) {
|
||||
thunk_param_type_ids.push_back(callee_return_type_id);
|
||||
}
|
||||
|
||||
auto carbon_thunk_function_id =
|
||||
MakeGeneratedFunctionDecl(context, loc_id,
|
||||
{.parent_scope_id = callee.parent_scope_id,
|
||||
.name_id = thunk_name_id,
|
||||
.param_type_ids = thunk_param_type_ids,
|
||||
.params_are_refs = true})
|
||||
.second;
|
||||
|
||||
BuildThunkDefinitionForExport(
|
||||
context, carbon_thunk_function_id, callee_function_id,
|
||||
context.functions().Get(carbon_thunk_function_id).first_decl_id(),
|
||||
callee.first_decl_id());
|
||||
|
||||
return carbon_thunk_function_id;
|
||||
}
|
||||
|
||||
auto ExportFunctionToCpp(Context& context, SemIR::LocId loc_id,
|
||||
SemIR::FunctionId callee_function_id)
|
||||
-> clang::FunctionDecl* {
|
||||
const SemIR::Function& callee = context.functions().Get(callee_function_id);
|
||||
|
||||
if (callee.generic_id.has_value()) {
|
||||
context.TODO(loc_id,
|
||||
"unsupported: C++ calling a Carbon function with "
|
||||
"generic parameters");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (callee.call_param_ranges.implicit_size() != 0) {
|
||||
context.TODO(loc_id,
|
||||
"unsupported: C++ calling a Carbon function with "
|
||||
"an implicit parameter");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Map the parent scope into the C++ AST.
|
||||
auto* decl_context =
|
||||
ExportNameScopeToCpp(context, loc_id, callee.parent_scope_id);
|
||||
if (!decl_context) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Get the parameter types of the Carbon function being
|
||||
// called. Exclude return params, if present.
|
||||
auto callee_function_params =
|
||||
context.inst_blocks().Get(callee.call_param_patterns_id);
|
||||
callee_function_params =
|
||||
callee_function_params.drop_back(callee.call_param_ranges.return_size());
|
||||
|
||||
llvm::SmallVector<SemIR::TypeId> callee_param_type_ids;
|
||||
for (auto callee_param_inst_id : callee_function_params) {
|
||||
auto scrutinee_type_id = ExtractScrutineeType(
|
||||
context.sem_ir(), context.insts().Get(callee_param_inst_id).type_id());
|
||||
callee_param_type_ids.push_back(scrutinee_type_id);
|
||||
}
|
||||
|
||||
// Create a Carbon thunk that calls the callee. The thunk's parameters
|
||||
// are all references so that the ABI is compatible with C++ callers.
|
||||
auto carbon_thunk_function_id = BuildCarbonToCarbonThunk(
|
||||
context, loc_id, callee_function_id, callee, callee_param_type_ids);
|
||||
|
||||
// Create a `clang::FunctionDecl` that can be used to call the Carbon thunk.
|
||||
auto* carbon_function_decl = BuildCppFunctionDeclForCarbonFn(
|
||||
context, loc_id, carbon_thunk_function_id);
|
||||
if (!carbon_function_decl) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Create a C++ thunk that calls the Carbon thunk.
|
||||
return BuildCppToCarbonThunk(context, loc_id, decl_context,
|
||||
context.names().GetFormatted(callee.name_id),
|
||||
carbon_function_decl, callee_param_type_ids,
|
||||
callee.GetDeclaredReturnType(context.sem_ir()));
|
||||
}
|
||||
|
||||
} // namespace Carbon::Check
|
||||
@@ -0,0 +1,40 @@
|
||||
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
||||
// Exceptions. See /LICENSE for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
|
||||
#ifndef CARBON_TOOLCHAIN_CHECK_CPP_EXPORT_H_
|
||||
#define CARBON_TOOLCHAIN_CHECK_CPP_EXPORT_H_
|
||||
|
||||
#include "clang/AST/Decl.h"
|
||||
#include "toolchain/check/context.h"
|
||||
#include "toolchain/sem_ir/ids.h"
|
||||
|
||||
namespace Carbon::Check {
|
||||
|
||||
// Exports a Carbon name scope into C++ as a namespace or class, or returns the
|
||||
// C++ namespace or class declaration that it was imported from.
|
||||
//
|
||||
// If the name scope has already been exported, returns the existing context.
|
||||
// Otherwise, creates a new C++ declaration context and returns it. Returns
|
||||
// nullptr if the name scope could not be exported and an error was diagnosed.
|
||||
auto ExportNameScopeToCpp(Context& context, SemIR::LocId loc_id,
|
||||
SemIR::NameScopeId name_scope_id)
|
||||
-> clang::DeclContext*;
|
||||
|
||||
// Exports a Carbon class into C++ as a class, or returns the C++ tag type that
|
||||
// the class was imported from.
|
||||
//
|
||||
// If the class has already been exported, returns the existing C++ class.
|
||||
// Otherwise, creates a new C++ class and returns it. Returns nullptr if the
|
||||
// class could not be exported and an error was diagnosed.
|
||||
auto ExportClassToCpp(Context& context, SemIR::LocId loc_id,
|
||||
SemIR::InstId class_inst_id, SemIR::ClassType class_type)
|
||||
-> clang::TagDecl*;
|
||||
|
||||
// Get a `clang::FunctionDecl` that can be used to call a Carbon function.
|
||||
auto ExportFunctionToCpp(Context& context, SemIR::LocId loc_id,
|
||||
SemIR::FunctionId function_id) -> clang::FunctionDecl*;
|
||||
|
||||
} // namespace Carbon::Check
|
||||
|
||||
#endif // CARBON_TOOLCHAIN_CHECK_CPP_EXPORT_H_
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <string>
|
||||
|
||||
#include "clang/AST/ASTContext.h"
|
||||
#include "clang/AST/Decl.h"
|
||||
#include "clang/Basic/FileManager.h"
|
||||
#include "clang/CodeGen/ModuleBuilder.h"
|
||||
#include "clang/Frontend/CompilerInstance.h"
|
||||
@@ -20,18 +21,27 @@
|
||||
#include "clang/Sema/MultiplexExternalSemaSource.h"
|
||||
#include "clang/Sema/Sema.h"
|
||||
#include "common/check.h"
|
||||
#include "common/map.h"
|
||||
#include "common/raw_string_ostream.h"
|
||||
#include "llvm/ADT/IntrusiveRefCntPtr.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include "llvm/Support/raw_ostream.h"
|
||||
#include "toolchain/base/kind_switch.h"
|
||||
#include "toolchain/check/context.h"
|
||||
#include "toolchain/check/cpp/access.h"
|
||||
#include "toolchain/check/cpp/export.h"
|
||||
#include "toolchain/check/cpp/import.h"
|
||||
#include "toolchain/check/cpp/location.h"
|
||||
#include "toolchain/check/cpp/type_mapping.h"
|
||||
#include "toolchain/check/import_ref.h"
|
||||
#include "toolchain/check/name_lookup.h"
|
||||
#include "toolchain/check/type_completion.h"
|
||||
#include "toolchain/diagnostics/diagnostic.h"
|
||||
#include "toolchain/diagnostics/emitter.h"
|
||||
#include "toolchain/diagnostics/format_providers.h"
|
||||
#include "toolchain/parse/node_ids.h"
|
||||
#include "toolchain/sem_ir/cpp_file.h"
|
||||
#include "toolchain/sem_ir/typed_insts.h"
|
||||
|
||||
namespace Carbon::Check {
|
||||
|
||||
@@ -45,31 +55,37 @@ static auto GenerateLineMarker(Context& context, llvm::raw_ostream& out,
|
||||
<< FormatEscaped(context.tokens().source().filename()) << "\"\n";
|
||||
}
|
||||
|
||||
// Appends a line marker and the specified `code` to `out`, adjusting the
|
||||
// `line` number if the `code_token` represents a block string literal.
|
||||
static auto AppendInlineCode(Context& context, llvm::raw_ostream& out,
|
||||
Lex::TokenIndex code_token, llvm::StringRef code)
|
||||
-> void {
|
||||
// Compute the line number on which the C++ code starts. Usually the code
|
||||
// is specified as a block string literal and starts on the line after the
|
||||
// start of the string token.
|
||||
// TODO: Determine if this is a block string literal without calling
|
||||
// `GetTokenText`, which re-lexes the string.
|
||||
int line = context.tokens().GetLineNumber(code_token);
|
||||
if (context.tokens().GetTokenText(code_token).contains('\n')) {
|
||||
++line;
|
||||
}
|
||||
|
||||
GenerateLineMarker(context, out, line);
|
||||
out << code << "\n";
|
||||
}
|
||||
|
||||
// Generates C++ file contents to #include all requested imports.
|
||||
static auto GenerateCppIncludesHeaderCode(
|
||||
Context& context, llvm::ArrayRef<Parse::Tree::PackagingNames> imports)
|
||||
-> std::string {
|
||||
std::string code;
|
||||
llvm::raw_string_ostream code_stream(code);
|
||||
RawStringOstream code_stream;
|
||||
for (const Parse::Tree::PackagingNames& import : imports) {
|
||||
if (import.inline_body_id.has_value()) {
|
||||
// Expand `import Cpp inline "code";` directly into the specified code.
|
||||
auto code_token = context.parse_tree().node_token(import.inline_body_id);
|
||||
|
||||
// Compute the line number on which the C++ code starts. Usually the code
|
||||
// is specified as a block string literal and starts on the line after the
|
||||
// start of the string token.
|
||||
// TODO: Determine if this is a block string literal without calling
|
||||
// `GetTokenText`, which re-lexes the string.
|
||||
int line = context.tokens().GetLineNumber(code_token);
|
||||
if (context.tokens().GetTokenText(code_token).contains('\n')) {
|
||||
++line;
|
||||
}
|
||||
|
||||
GenerateLineMarker(context, code_stream, line);
|
||||
code_stream << context.string_literal_values().Get(
|
||||
context.tokens().GetStringLiteralValue(code_token))
|
||||
<< "\n";
|
||||
AppendInlineCode(context, code_stream, code_token,
|
||||
context.string_literal_values().Get(
|
||||
context.tokens().GetStringLiteralValue(code_token)));
|
||||
// TODO: Inject a clang pragma here to produce an error if there are
|
||||
// unclosed scopes at the end of this inline C++ fragment.
|
||||
} else if (import.library_id.has_value()) {
|
||||
@@ -86,7 +102,7 @@ static auto GenerateCppIncludesHeaderCode(
|
||||
}
|
||||
}
|
||||
}
|
||||
return code;
|
||||
return code_stream.TakeStr();
|
||||
}
|
||||
|
||||
// Adds the given source location and an `ImportIRInst` referring to it in
|
||||
@@ -306,83 +322,164 @@ class ShallowCopyCompilerInvocation : public clang::CompilerInvocation {
|
||||
}
|
||||
};
|
||||
|
||||
// Provides clang AST nodes representing Carbon SemIR entities.
|
||||
class CarbonExternalASTSource : public clang::ExternalASTSource {
|
||||
public:
|
||||
explicit CarbonExternalASTSource(Context* context,
|
||||
clang::ASTContext* ast_context)
|
||||
: context_(context), ast_context_(ast_context) {}
|
||||
explicit CarbonExternalASTSource(Context* context) : context_(context) {}
|
||||
|
||||
auto StartTranslationUnit(clang::ASTConsumer* consumer) -> void override;
|
||||
|
||||
// Look up decls for `decl_name` inside `decl_context`, adding the decls to
|
||||
// `decl_context`. Returns true if any decls were added.
|
||||
auto FindExternalVisibleDeclsByName(
|
||||
const clang::DeclContext* decl_context, clang::DeclarationName decl_name,
|
||||
const clang::DeclContext* original_decl_context) -> bool override;
|
||||
|
||||
auto StartTranslationUnit(clang::ASTConsumer* consumer) -> void override;
|
||||
auto CompleteType(clang::TagDecl* tag_decl) -> void override;
|
||||
|
||||
auto layoutRecordType(
|
||||
const clang::RecordDecl* record_decl, uint64_t& size, uint64_t& alignment,
|
||||
llvm::DenseMap<const clang::FieldDecl*, uint64_t>& field_offsets,
|
||||
llvm::DenseMap<const clang::CXXRecordDecl*, clang::CharUnits>&
|
||||
base_offsets,
|
||||
llvm::DenseMap<const clang::CXXRecordDecl*, clang::CharUnits>&
|
||||
vbase_offsets) -> bool override;
|
||||
|
||||
private:
|
||||
// Builds the top-level C++ namespace `Carbon` and adds it to the translation
|
||||
// unit.
|
||||
auto BuildCarbonNamespace() -> void;
|
||||
|
||||
// Map a Carbon entity to a Clang NamedDecl. Returns null if the entity cannot
|
||||
// currently be represented in C++.
|
||||
auto MapInstIdToClangDeclOrType(LookupResult lookup)
|
||||
-> std::variant<clang::NamedDecl*, clang::QualType>;
|
||||
|
||||
// Get a current best-effort location for the current position within C++
|
||||
// processing.
|
||||
auto GetCurrentCppLocId() -> SemIR::LocId {
|
||||
auto* cpp_context = context_->cpp_context();
|
||||
CARBON_CHECK(cpp_context);
|
||||
|
||||
// Use the current token location when parsing.
|
||||
auto clang_source_loc = cpp_context->parser().getCurToken().getLocation();
|
||||
if (auto& code_synthesis_contexts =
|
||||
cpp_context->sema().CodeSynthesisContexts;
|
||||
!code_synthesis_contexts.empty()) {
|
||||
// Use the current point of instantiation during template instantiation.
|
||||
clang_source_loc = code_synthesis_contexts.back().PointOfInstantiation;
|
||||
}
|
||||
|
||||
// TODO: Refactor with AddImportIRInst in import.cpp.
|
||||
SemIR::ClangSourceLocId clang_source_loc_id =
|
||||
context_->sem_ir().clang_source_locs().Add(clang_source_loc);
|
||||
return context_->import_ir_insts().Add(
|
||||
SemIR::ImportIRInst(clang_source_loc_id));
|
||||
}
|
||||
|
||||
Check::Context* context_;
|
||||
clang::ASTContext* ast_context_;
|
||||
clang::NamespaceDecl* carbon_cpp_namespace_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
void CarbonExternalASTSource::StartTranslationUnit(
|
||||
clang::ASTConsumer* /*Consumer*/) {
|
||||
auto& translation_unit = *ast_context_->getTranslationUnitDecl();
|
||||
// Mark the translation unit as having external storage so we get a query for
|
||||
// the `Carbon` namespace in the top level/translation unit scope.
|
||||
translation_unit.setHasExternalVisibleStorage();
|
||||
BuildCarbonNamespace();
|
||||
}
|
||||
|
||||
// Map a Carbon entity to a Clang NamedDecl.
|
||||
static auto MapInstIdToClangDecl(Context& context,
|
||||
clang::ASTContext& ast_context,
|
||||
clang::DeclContext& decl_context,
|
||||
LookupResult lookup) -> clang::NamedDecl* {
|
||||
auto CarbonExternalASTSource::MapInstIdToClangDeclOrType(LookupResult lookup)
|
||||
-> std::variant<clang::NamedDecl*, clang::QualType> {
|
||||
auto target_inst_id = lookup.scope_result.target_inst_id();
|
||||
if (auto target_inst =
|
||||
context.insts().TryGetAs<SemIR::Namespace>(target_inst_id)) {
|
||||
auto& name_scope = context.name_scopes().Get(target_inst->name_scope_id);
|
||||
auto* identifier_info =
|
||||
GetClangIdentifierInfo(context, name_scope.name_id());
|
||||
// TODO: Don't immediately use the decl_context - build any intermediate
|
||||
// namespaces iteratively.
|
||||
// Eventually add a mapping and use that/populate it/keep it up to date.
|
||||
// decl_context could be prepopulated in that mapping and not passed
|
||||
// explicitly to MapInstIdToClangDecl.
|
||||
return clang::NamespaceDecl::Create(
|
||||
ast_context, &decl_context, false, clang::SourceLocation(),
|
||||
clang::SourceLocation(), identifier_info, nullptr, false);
|
||||
auto target_const_id = context_->constant_values().Get(target_inst_id);
|
||||
auto target_inst = context_->constant_values().GetInst(target_const_id);
|
||||
|
||||
if (target_inst.type_id() == SemIR::TypeType::TypeId) {
|
||||
auto type_id =
|
||||
context_->types().GetTypeIdForTypeConstantId(target_const_id);
|
||||
auto type = MapToCppType(*context_, type_id);
|
||||
if (type.isNull()) {
|
||||
context_->TODO(GetCurrentCppLocId(), "interop with unsupported type");
|
||||
return nullptr;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
return nullptr;
|
||||
|
||||
CARBON_KIND_SWITCH(target_inst) {
|
||||
case CARBON_KIND(SemIR::Namespace namespace_info): {
|
||||
auto* decl_context =
|
||||
ExportNameScopeToCpp(*context_, SemIR::LocId(target_inst_id),
|
||||
namespace_info.name_scope_id);
|
||||
if (!decl_context) {
|
||||
return nullptr;
|
||||
}
|
||||
if (isa<clang::TranslationUnitDecl>(decl_context)) {
|
||||
context_->TODO(GetCurrentCppLocId(),
|
||||
"interop with translation unit decl");
|
||||
return nullptr;
|
||||
}
|
||||
return cast<clang::NamedDecl>(decl_context);
|
||||
}
|
||||
case SemIR::StructValue::Kind: {
|
||||
auto callee = GetCallee(context_->sem_ir(), target_inst_id);
|
||||
auto* callee_function = std::get_if<SemIR::CalleeFunction>(&callee);
|
||||
if (!callee_function) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const SemIR::Function& function =
|
||||
context_->functions().Get(callee_function->function_id);
|
||||
if (function.clang_decl_id.has_value()) {
|
||||
return cast<clang::NamedDecl>(
|
||||
context_->clang_decls().Get(function.clang_decl_id).key.decl);
|
||||
}
|
||||
|
||||
return ExportFunctionToCpp(*context_, SemIR::LocId(target_inst_id),
|
||||
callee_function->function_id);
|
||||
}
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
auto CarbonExternalASTSource::BuildCarbonNamespace() -> void {
|
||||
static const llvm::StringLiteral carbon_namespace_name = "Carbon";
|
||||
auto& ast_context = context_->ast_context();
|
||||
auto* identifier = &ast_context.Idents.get(carbon_namespace_name);
|
||||
|
||||
// Create the namespace and add it to the translation unit scope.
|
||||
auto* decl_context = ast_context.getTranslationUnitDecl();
|
||||
auto* carbon_cpp_namespace = clang::NamespaceDecl::Create(
|
||||
ast_context, decl_context, /*Inline=*/false, clang::SourceLocation(),
|
||||
clang::SourceLocation(), identifier, /*PrevDecl=*/nullptr,
|
||||
/*Nested=*/false);
|
||||
decl_context->addDecl(carbon_cpp_namespace);
|
||||
|
||||
// We provide custom lookup results within this namespace.
|
||||
carbon_cpp_namespace->setHasExternalVisibleStorage();
|
||||
|
||||
// Register this file's package scope as corresponding to the `Carbon`
|
||||
// namespace in C++.
|
||||
// TODO: For mangling purposes, include the package as a sub-namespace.
|
||||
auto key = SemIR::ClangDeclKey::ForNonFunctionDecl(carbon_cpp_namespace);
|
||||
auto clang_decl_id = context_->clang_decls().Add(
|
||||
{.key = key, .inst_id = SemIR::Namespace::PackageInstId});
|
||||
context_->name_scopes()
|
||||
.Get(SemIR::NameScopeId::Package)
|
||||
.set_clang_decl_context_id(clang_decl_id, /*is_cpp_scope=*/false);
|
||||
}
|
||||
|
||||
auto CarbonExternalASTSource::FindExternalVisibleDeclsByName(
|
||||
const clang::DeclContext* decl_context, clang::DeclarationName decl_name,
|
||||
const clang::DeclContext* /*OriginalDC*/) -> bool {
|
||||
if (decl_context != carbon_cpp_namespace_) {
|
||||
if (decl_context->getDeclKind() != clang::Decl::Kind::TranslationUnit) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static const llvm::StringLiteral carbon_namespace_name = "Carbon";
|
||||
if (auto* identifier = decl_name.getAsIdentifierInfo();
|
||||
!identifier || !identifier->isStr(carbon_namespace_name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Build the top level 'Carbon' namespace
|
||||
auto& ast_context = decl_context->getParentASTContext();
|
||||
auto& mutable_tu_decl_context = *ast_context.getTranslationUnitDecl();
|
||||
carbon_cpp_namespace_ = clang::NamespaceDecl::Create(
|
||||
ast_context, &mutable_tu_decl_context, false, clang::SourceLocation(),
|
||||
clang::SourceLocation(), &ast_context.Idents.get(carbon_namespace_name),
|
||||
nullptr, false);
|
||||
carbon_cpp_namespace_->setHasExternalVisibleStorage();
|
||||
SetExternalVisibleDeclsForName(decl_context, decl_name,
|
||||
{carbon_cpp_namespace_});
|
||||
return true;
|
||||
}
|
||||
|
||||
// Lookup the name in Carbon package scope
|
||||
// Find the Carbon declaration corresponding to this Clang declaration.
|
||||
auto* decl = cast<clang::Decl>(
|
||||
const_cast<clang::DeclContext*>(decl_context->getPrimaryContext()));
|
||||
auto key = SemIR::ClangDeclKey::ForNonFunctionDecl(decl);
|
||||
auto decl_id = context_->clang_decls().Lookup(key);
|
||||
CARBON_CHECK(
|
||||
decl_id.has_value(),
|
||||
"The DeclContext should already be associated with a Carbon InstId.");
|
||||
auto decl_context_inst_id = context_->clang_decls().Get(decl_id).inst_id;
|
||||
|
||||
llvm::SmallVector<Check::LookupScope> lookup_scopes;
|
||||
|
||||
@@ -390,7 +487,7 @@ auto CarbonExternalASTSource::FindExternalVisibleDeclsByName(
|
||||
// here - completeness should've been checked by clang before this point.
|
||||
if (!AppendLookupScopesForConstant(
|
||||
*context_, SemIR::LocId::None,
|
||||
context_->constant_values().Get(SemIR::Namespace::PackageInstId),
|
||||
context_->constant_values().Get(decl_context_inst_id),
|
||||
SemIR::ConstantId::None, &lookup_scopes)) {
|
||||
return false;
|
||||
}
|
||||
@@ -413,16 +510,215 @@ auto CarbonExternalASTSource::FindExternalVisibleDeclsByName(
|
||||
}
|
||||
|
||||
// Map the found Carbon entity to a Clang NamedDecl.
|
||||
auto* clang_decl = MapInstIdToClangDecl(*context_, *ast_context_,
|
||||
*carbon_cpp_namespace_, result);
|
||||
if (!clang_decl) {
|
||||
CARBON_KIND_SWITCH(MapInstIdToClangDeclOrType(result)) {
|
||||
case CARBON_KIND(clang::NamedDecl* clang_decl): {
|
||||
if (clang_decl) {
|
||||
SetExternalVisibleDeclsForName(decl_context, decl_name, {clang_decl});
|
||||
return true;
|
||||
} else {
|
||||
SetNoExternalVisibleDeclsForName(decl_context, decl_name);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
case CARBON_KIND(clang::QualType type): {
|
||||
// Create a typedef declaration to model the type result.
|
||||
// TODO: If the type is a tag type that was declared with this name in
|
||||
// this context, use the tag decl directly.
|
||||
auto& ast_context = context_->ast_context();
|
||||
auto loc = GetCppLocation(
|
||||
*context_, SemIR::LocId(result.scope_result.target_inst_id()));
|
||||
auto* typedef_decl = clang::TypedefDecl::Create(
|
||||
ast_context, const_cast<clang::DeclContext*>(decl_context), loc, loc,
|
||||
identifier, ast_context.getTrivialTypeSourceInfo(type, loc));
|
||||
if (isa<clang::CXXRecordDecl>(decl_context)) {
|
||||
typedef_decl->setAccess(
|
||||
MapToCppAccess(result.scope_result.access_kind()));
|
||||
}
|
||||
SetExternalVisibleDeclsForName(decl_context, decl_name, {typedef_decl});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If this declaration declares a class type that is "owned" by Carbon, and not
|
||||
// imported from C++, returns the corresponding type ID and `ClassType`.
|
||||
// Otherwise returns `nullopt`.
|
||||
static auto GetAsCarbonOwnedClass(Context& context,
|
||||
const clang::TagDecl* tag_decl)
|
||||
-> std::optional<std::pair<SemIR::TypeId, SemIR::ClassType>> {
|
||||
// Quickly check whether we could possibly own this class.
|
||||
// TODO: Once we multiplex with the ASTReader, handle
|
||||
// ASTReader::completeVisibleDeclsMap setting this to `false`.
|
||||
if (!tag_decl->hasExternalVisibleStorage()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto key = SemIR::ClangDeclKey::ForNonFunctionDecl(
|
||||
const_cast<clang::TagDecl*>(tag_decl->getFirstDecl()));
|
||||
auto clang_decl_id = context.clang_decls().Lookup(key);
|
||||
if (!clang_decl_id.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto inst_id = context.clang_decls().Get(clang_decl_id).inst_id;
|
||||
auto const_id = context.constant_values().Get(inst_id);
|
||||
if (!const_id.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto class_type =
|
||||
context.constant_values().TryGetInstAs<SemIR::ClassType>(const_id);
|
||||
if (!class_type) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Determine whether this class was imported from C++.
|
||||
// TODO: This currently can't happen, because only Carbon classes have
|
||||
// external lexical storage, but will happen once we support importing C++
|
||||
// classes from AST files. Add a test once that is supported.
|
||||
// TODO: Consider setting `extern_library_id` on classes imported from C++ to
|
||||
// indicate the current file does not own them.
|
||||
const auto& class_info = context.classes().Get(class_type->class_id);
|
||||
if (class_info.parent_scope_id.has_value() &&
|
||||
context.name_scopes().Get(class_info.parent_scope_id).is_cpp_scope()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto class_type_id = context.types().GetTypeIdForTypeConstantId(const_id);
|
||||
return std::make_pair(class_type_id, *class_type);
|
||||
}
|
||||
|
||||
auto CarbonExternalASTSource::CompleteType(clang::TagDecl* tag_decl) -> void {
|
||||
auto* class_decl = dyn_cast<clang::CXXRecordDecl>(tag_decl);
|
||||
if (!class_decl) {
|
||||
// TODO: If we start producing clang EnumTypes, we may have to handle them
|
||||
// here too.
|
||||
return;
|
||||
}
|
||||
|
||||
auto carbon_class_info = GetAsCarbonOwnedClass(*context_, tag_decl);
|
||||
if (!carbon_class_info) {
|
||||
return;
|
||||
}
|
||||
auto& [class_type_id, class_type] = *carbon_class_info;
|
||||
|
||||
auto context_fn = [](DiagnosticContextBuilder& /*builder*/) -> void {};
|
||||
if (!RequireCompleteType(*context_, class_type_id, GetCurrentCppLocId(),
|
||||
context_fn)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& class_info = context_->classes().Get(class_type.class_id);
|
||||
class_decl->startDefinition();
|
||||
CARBON_CHECK(class_decl->hasDefinition());
|
||||
|
||||
// If the Carbon class has a base class that we can map into C++, add that as
|
||||
// a C++ base class.
|
||||
auto base_type_id =
|
||||
class_info.GetBaseType(context_->sem_ir(), class_type.specific_id);
|
||||
if (base_type_id.has_value()) {
|
||||
auto base_loc = GetCppLocation(*context_, SemIR::LocId(class_info.base_id));
|
||||
if (auto base_type = MapToCppType(*context_, base_type_id);
|
||||
!base_type.isNull() && base_type->isStructureOrClassType() &&
|
||||
!context_->clang_sema().RequireCompleteType(
|
||||
base_loc, base_type, clang::diag::err_incomplete_base_class)) {
|
||||
bool is_virtual = false;
|
||||
bool is_base_of_class = true;
|
||||
clang::CXXBaseSpecifier base(
|
||||
clang::SourceRange(base_loc, base_loc), is_virtual, is_base_of_class,
|
||||
clang::AS_public,
|
||||
context_->ast_context().getTrivialTypeSourceInfo(base_type, base_loc),
|
||||
/*EllipsisLoc=*/clang::SourceLocation());
|
||||
clang::CXXBaseSpecifier* bases[1] = {&base};
|
||||
CARBON_CHECK(class_decl->hasDefinition());
|
||||
class_decl->setBases(bases, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Import fields, plus any special member functions that affect class
|
||||
// properties.
|
||||
class_decl->completeDefinition();
|
||||
}
|
||||
|
||||
auto CarbonExternalASTSource::layoutRecordType(
|
||||
const clang::RecordDecl* record_decl, uint64_t& size, uint64_t& alignment,
|
||||
llvm::DenseMap<const clang::FieldDecl*, uint64_t>& field_offsets,
|
||||
llvm::DenseMap<const clang::CXXRecordDecl*, clang::CharUnits>& base_offsets,
|
||||
llvm::DenseMap<const clang::CXXRecordDecl*, clang::CharUnits>&
|
||||
vbase_offsets) -> bool {
|
||||
auto carbon_class_info = GetAsCarbonOwnedClass(*context_, record_decl);
|
||||
if (!carbon_class_info) {
|
||||
return false;
|
||||
}
|
||||
auto& [class_type_id, class_type] = *carbon_class_info;
|
||||
|
||||
// Clang should not have asked for the layout of an incomplete type, but check
|
||||
// now to be sure, and to generate a specific definition if needed.
|
||||
// TODO: Add a test for layout of a specific class once they're supported in
|
||||
// general.
|
||||
CompleteTypeOrCheckFail(*context_, class_type_id);
|
||||
|
||||
// Set the overall size and alignment. We round up the size to an integer
|
||||
// number of bytes in order to avoid surprising Clang too much.
|
||||
auto layout = context_->sem_ir()
|
||||
.types()
|
||||
.GetCompleteTypeInfo(class_type_id)
|
||||
.object_layout;
|
||||
size = layout.size.bytes() * 8;
|
||||
alignment = layout.alignment.bits();
|
||||
|
||||
// TODO: Add field offsets once we import fields.
|
||||
static_cast<void>(field_offsets);
|
||||
|
||||
// Add offset for base class, if any.
|
||||
if (const auto* class_decl = dyn_cast<clang::CXXRecordDecl>(record_decl);
|
||||
class_decl && !class_decl->bases().empty()) {
|
||||
CARBON_CHECK(class_decl->getNumBases() == 1,
|
||||
"Carbon class with multiple bases");
|
||||
const auto& base = *class_decl->bases_begin();
|
||||
// TODO: If this class introduced a vptr, the base will be at an offset of
|
||||
// `sizeof(void*)`, not 0.
|
||||
base_offsets.insert(
|
||||
{base.getType()->getAsCXXRecordDecl()->getCanonicalDecl(),
|
||||
clang::CharUnits::Zero()});
|
||||
|
||||
// TODO: Support deriving from a C++ class with virtual bases.
|
||||
CARBON_CHECK(class_decl->getNumVBases() == 0,
|
||||
"Carbon class with multiple bases");
|
||||
static_cast<void>(vbase_offsets);
|
||||
}
|
||||
|
||||
SetExternalVisibleDeclsForName(decl_context, decl_name, {clang_decl});
|
||||
return true;
|
||||
}
|
||||
|
||||
// Parses a sequence of top-level declarations and forms a corresponding
|
||||
// representation in the Clang AST. Unlike clang::ParseAST, does not finish the
|
||||
// translation unit when EOF is reached.
|
||||
static auto ParseTopLevelDecls(clang::Parser& parser,
|
||||
clang::ASTConsumer& consumer) -> void {
|
||||
// Don't allow C++20 module declarations in inline Cpp code fragments.
|
||||
auto module_import_state = clang::Sema::ModuleImportState::NotACXX20Module;
|
||||
|
||||
// Parse top-level declarations until we see EOF. Do not parse EOF, as that
|
||||
// will cause the parser to end the translation unit prematurely.
|
||||
while (parser.getCurToken().isNot(clang::tok::eof)) {
|
||||
clang::Parser::DeclGroupPtrTy decl_group;
|
||||
bool eof = parser.ParseTopLevelDecl(decl_group, module_import_state);
|
||||
CARBON_CHECK(!eof, "Should not parse decls at EOF");
|
||||
if (decl_group && !consumer.HandleTopLevelDecl(decl_group.get())) {
|
||||
// If the consumer rejects the declaration, bail out of parsing.
|
||||
//
|
||||
// TODO: In this case, we shouldn't parse any more declarations even in
|
||||
// separate inline C++ fragments. But our current AST consumer only ever
|
||||
// returns true.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// An action and a set of registered Clang callbacks used to generate an AST
|
||||
// from a set of Cpp imports.
|
||||
class GenerateASTAction : public clang::ASTFrontendAction {
|
||||
@@ -470,30 +766,18 @@ class GenerateASTAction : public clang::ASTFrontendAction {
|
||||
auto& parser = *parser_ptr;
|
||||
|
||||
clang_instance.getPreprocessor().EnterMainSourceFile();
|
||||
if (auto* source = clang_instance.getASTContext().getExternalSource()) {
|
||||
source->StartTranslationUnit(&clang_instance.getASTConsumer());
|
||||
}
|
||||
|
||||
parser.Initialize();
|
||||
clang_instance.getSema().ActOnStartOfTranslationUnit();
|
||||
|
||||
context_->set_cpp_context(
|
||||
std::make_unique<CppContext>(clang_instance, std::move(parser_ptr)));
|
||||
|
||||
// Don't allow C++20 module declarations in inline Cpp code fragments.
|
||||
auto module_import_state = clang::Sema::ModuleImportState::NotACXX20Module;
|
||||
|
||||
// Parse top-level declarations until we see EOF. Do not parse EOF, as that
|
||||
// will cause the parser to end the translation unit prematurely.
|
||||
while (parser.getCurToken().isNot(clang::tok::eof)) {
|
||||
clang::Parser::DeclGroupPtrTy decl_group;
|
||||
bool eof = parser.ParseTopLevelDecl(decl_group, module_import_state);
|
||||
CARBON_CHECK(!eof);
|
||||
if (decl_group && !clang_instance.getASTConsumer().HandleTopLevelDecl(
|
||||
decl_group.get())) {
|
||||
break;
|
||||
}
|
||||
if (auto* source = clang_instance.getASTContext().getExternalSource()) {
|
||||
source->StartTranslationUnit(&clang_instance.getASTConsumer());
|
||||
}
|
||||
|
||||
clang_instance.getSema().ActOnStartOfTranslationUnit();
|
||||
|
||||
ParseTopLevelDecls(parser, clang_instance.getASTConsumer());
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -567,7 +851,7 @@ auto GenerateAst(Context& context,
|
||||
// support. Implement multiplexing support (possibly in Clang) to restore
|
||||
// modules functionality.
|
||||
ast.setExternalSource(
|
||||
llvm::makeIntrusiveRefCnt<CarbonExternalASTSource>(&context, &ast));
|
||||
llvm::makeIntrusiveRefCnt<CarbonExternalASTSource>(&context));
|
||||
|
||||
if (llvm::Error error = action.Execute()) {
|
||||
// `Execute` currently never fails, but its contract allows it to.
|
||||
@@ -583,6 +867,40 @@ auto GenerateAst(Context& context,
|
||||
return true;
|
||||
}
|
||||
|
||||
auto InjectAstFromInlineCode(Context& context, SemIR::LocId loc_id,
|
||||
llvm::StringRef source_code) -> void {
|
||||
auto* cpp_context = context.cpp_context();
|
||||
CARBON_CHECK(cpp_context);
|
||||
|
||||
clang::Sema& sema = cpp_context->sema();
|
||||
clang::Preprocessor& preprocessor = sema.getPreprocessor();
|
||||
clang::Parser& parser = cpp_context->parser();
|
||||
|
||||
RawStringOstream code_stream;
|
||||
AppendInlineCode(context, code_stream,
|
||||
context.parse_tree().node_token(loc_id.node_id()),
|
||||
source_code);
|
||||
|
||||
auto buffer = llvm::MemoryBuffer::getMemBufferCopy(code_stream.TakeStr(),
|
||||
"<inline c++>");
|
||||
clang::FileID file_id =
|
||||
preprocessor.getSourceManager().createFileID(std::move(buffer));
|
||||
|
||||
if (preprocessor.EnterSourceFile(file_id, nullptr, clang::SourceLocation())) {
|
||||
// Clang will have generated a suitable error. There's nothing more to do
|
||||
// here.
|
||||
return;
|
||||
}
|
||||
|
||||
// The parser will typically have an EOF as its cached current token; consume
|
||||
// that so we can reach the newly-injected tokens.
|
||||
if (parser.getCurToken().is(clang::tok::eof)) {
|
||||
parser.ConsumeToken();
|
||||
}
|
||||
|
||||
ParseTopLevelDecls(parser, sema.getASTConsumer());
|
||||
}
|
||||
|
||||
auto FinishAst(Context& context) -> void {
|
||||
if (!context.cpp_context()) {
|
||||
return;
|
||||
|
||||
@@ -5,10 +5,15 @@
|
||||
#ifndef CARBON_TOOLCHAIN_CHECK_CPP_GENERATE_AST_H_
|
||||
#define CARBON_TOOLCHAIN_CHECK_CPP_GENERATE_AST_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "llvm/ADT/ArrayRef.h"
|
||||
#include "llvm/ADT/IntrusiveRefCntPtr.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include "llvm/Support/VirtualFileSystem.h"
|
||||
#include "toolchain/check/context.h"
|
||||
#include "toolchain/parse/tree.h"
|
||||
#include "toolchain/sem_ir/ids.h"
|
||||
|
||||
namespace Carbon::Check {
|
||||
|
||||
@@ -22,6 +27,11 @@ auto GenerateAst(Context& context,
|
||||
std::shared_ptr<clang::CompilerInvocation> base_invocation)
|
||||
-> bool;
|
||||
|
||||
// Injects C++ code from `inline Cpp` into the active Clang AST context.
|
||||
// Returns a bool representing whether parsing was successful.
|
||||
auto InjectAstFromInlineCode(Context& context, SemIR::LocId loc_id,
|
||||
llvm::StringRef source_code) -> void;
|
||||
|
||||
// Finishes AST generation for the given checking context. Performs end of file
|
||||
// steps such as template instantiation and warning on unused declarations.
|
||||
auto FinishAst(Context& context) -> void;
|
||||
|
||||
@@ -15,26 +15,34 @@
|
||||
#include "toolchain/check/import_ref.h"
|
||||
#include "toolchain/check/inst.h"
|
||||
#include "toolchain/check/type.h"
|
||||
#include "toolchain/sem_ir/builtin_function_kind.h"
|
||||
#include "toolchain/sem_ir/ids.h"
|
||||
#include "toolchain/sem_ir/typed_insts.h"
|
||||
|
||||
namespace Carbon::Check {
|
||||
|
||||
// If the given type is a C++ class type, returns the corresponding class
|
||||
// declaration. Otherwise returns nullptr.
|
||||
// TODO: Handle qualified types.
|
||||
static auto TypeAsClassDecl(Context& context,
|
||||
SemIR::ConstantId query_self_const_id)
|
||||
-> clang::CXXRecordDecl* {
|
||||
auto self_inst_id = context.constant_values().GetInstId(query_self_const_id);
|
||||
auto class_type = context.insts().TryGetAs<SemIR::ClassType>(self_inst_id);
|
||||
// Given a type constant, return the corresponding class scope if there is one.
|
||||
static auto GetClassScope(Context& context,
|
||||
SemIR::ConstantId query_self_const_id)
|
||||
-> SemIR::NameScopeId {
|
||||
auto class_type = context.constant_values().TryGetInstAs<SemIR::ClassType>(
|
||||
query_self_const_id);
|
||||
if (!class_type) {
|
||||
// Not a class.
|
||||
return nullptr;
|
||||
return SemIR::NameScopeId::None;
|
||||
}
|
||||
|
||||
return context.classes().Get(class_type->class_id).scope_id;
|
||||
}
|
||||
|
||||
// If the given type is a C++ tag (class or enumeration) type, returns the
|
||||
// corresponding tag declaration. Otherwise returns nullptr.
|
||||
// TODO: Handle qualified types.
|
||||
static auto TypeAsTagDecl(Context& context,
|
||||
SemIR::ConstantId query_self_const_id)
|
||||
-> clang::TagDecl* {
|
||||
SemIR::NameScopeId class_scope_id =
|
||||
context.classes().Get(class_type->class_id).scope_id;
|
||||
GetClassScope(context, query_self_const_id);
|
||||
if (!class_scope_id.has_value()) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -45,8 +53,16 @@ static auto TypeAsClassDecl(Context& context,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return dyn_cast<clang::CXXRecordDecl>(
|
||||
context.clang_decls().Get(decl_id).key.decl);
|
||||
return dyn_cast<clang::TagDecl>(context.clang_decls().Get(decl_id).key.decl);
|
||||
}
|
||||
|
||||
// If the given type is a C++ class type, returns the corresponding class
|
||||
// declaration. Otherwise returns nullptr.
|
||||
static auto TypeAsClassDecl(Context& context,
|
||||
SemIR::ConstantId query_self_const_id)
|
||||
-> clang::CXXRecordDecl* {
|
||||
return dyn_cast_or_null<clang::CXXRecordDecl>(
|
||||
TypeAsTagDecl(context, query_self_const_id));
|
||||
}
|
||||
|
||||
namespace {
|
||||
@@ -61,9 +77,7 @@ struct DeclInfo {
|
||||
// Returns SemIR::InstId::None if a C++ function is not found, and
|
||||
// SemIR::ErrorInst::InstId if an error occurs.
|
||||
static auto GetFunctionId(Context& context, SemIR::LocId loc_id,
|
||||
DeclInfo decl_info,
|
||||
const TypeStructure* best_impl_type_structure,
|
||||
SemIR::LocId best_impl_loc_id) -> SemIR::InstId {
|
||||
DeclInfo decl_info) -> SemIR::InstId {
|
||||
if (!decl_info.decl) {
|
||||
// The C++ type is not able to implement the interface.
|
||||
return SemIR::InstId::None;
|
||||
@@ -85,71 +99,41 @@ static auto GetFunctionId(Context& context, SemIR::LocId loc_id,
|
||||
context, loc_id, clang::DeclAccessPair::make(cpp_fn, cpp_fn->getAccess()),
|
||||
context.insts().GetAsKnownInstId<SemIR::FunctionDecl>(fn_id));
|
||||
|
||||
// TODO: Infer a C++ type structure and check whether it's less strict than
|
||||
// the best Carbon type structure.
|
||||
static_cast<void>(best_impl_type_structure);
|
||||
static_cast<void>(best_impl_loc_id);
|
||||
|
||||
return fn_id;
|
||||
}
|
||||
|
||||
static auto BuildCopyWitness(
|
||||
Context& context, SemIR::LocId loc_id,
|
||||
SemIR::ConstantId query_self_const_id,
|
||||
SemIR::SpecificInterfaceId query_specific_interface_id,
|
||||
const TypeStructure* best_impl_type_structure,
|
||||
SemIR::LocId best_impl_loc_id) -> SemIR::InstId {
|
||||
SemIR::SpecificInterfaceId query_specific_interface_id) -> SemIR::InstId {
|
||||
auto& clang_sema = context.clang_sema();
|
||||
|
||||
// TODO: This should provide `Copy` for enums and other trivially copyable
|
||||
// types.
|
||||
auto* class_decl = TypeAsClassDecl(context, query_self_const_id);
|
||||
if (!class_decl) {
|
||||
auto* tag_decl = TypeAsTagDecl(context, query_self_const_id);
|
||||
if (!tag_decl) {
|
||||
return SemIR::InstId::None;
|
||||
}
|
||||
auto decl_info = DeclInfo{.decl = clang_sema.LookupCopyingConstructor(
|
||||
class_decl, clang::Qualifiers::Const),
|
||||
.signature = {.num_params = 1}};
|
||||
auto fn_id = GetFunctionId(context, loc_id, decl_info,
|
||||
best_impl_type_structure, best_impl_loc_id);
|
||||
if (fn_id == SemIR::ErrorInst::InstId || fn_id == SemIR::InstId::None) {
|
||||
return fn_id;
|
||||
if (auto* class_decl = dyn_cast<clang::CXXRecordDecl>(tag_decl)) {
|
||||
auto decl_info = DeclInfo{.decl = clang_sema.LookupCopyingConstructor(
|
||||
class_decl, clang::Qualifiers::Const),
|
||||
.signature = {.num_params = 1}};
|
||||
auto fn_id = GetFunctionId(context, loc_id, decl_info);
|
||||
if (fn_id == SemIR::ErrorInst::InstId || fn_id == SemIR::InstId::None) {
|
||||
return fn_id;
|
||||
}
|
||||
return BuildCustomWitness(context, loc_id, query_self_const_id,
|
||||
query_specific_interface_id, {fn_id});
|
||||
}
|
||||
return BuildCustomWitness(context, loc_id, query_self_const_id,
|
||||
query_specific_interface_id, {fn_id});
|
||||
}
|
||||
|
||||
static auto BuildDestroyWitness(
|
||||
Context& context, SemIR::LocId loc_id,
|
||||
SemIR::ConstantId query_self_const_id,
|
||||
SemIR::SpecificInterfaceId query_specific_interface_id,
|
||||
const TypeStructure* best_impl_type_structure,
|
||||
SemIR::LocId best_impl_loc_id) -> SemIR::InstId {
|
||||
auto& clang_sema = context.clang_sema();
|
||||
|
||||
// TODO: This should provide `Destroy` for enums and other trivially
|
||||
// destructible types.
|
||||
auto* class_decl = TypeAsClassDecl(context, query_self_const_id);
|
||||
if (!class_decl) {
|
||||
return SemIR::InstId::None;
|
||||
}
|
||||
auto decl_info = DeclInfo{.decl = clang_sema.LookupDestructor(class_decl),
|
||||
.signature = {.num_params = 0}};
|
||||
auto fn_id = GetFunctionId(context, loc_id, decl_info,
|
||||
best_impl_type_structure, best_impl_loc_id);
|
||||
if (fn_id == SemIR::ErrorInst::InstId || fn_id == SemIR::InstId::None) {
|
||||
return fn_id;
|
||||
}
|
||||
return BuildCustomWitness(context, loc_id, query_self_const_id,
|
||||
query_specific_interface_id, {fn_id});
|
||||
// Otherwise it's an enum (or eventually a C struct type). Perform a primitive
|
||||
// copy.
|
||||
return BuildPrimitiveCopyWitness(
|
||||
context, loc_id, GetClassScope(context, query_self_const_id),
|
||||
query_self_const_id, query_specific_interface_id);
|
||||
}
|
||||
|
||||
static auto BuildCppUnsafeDerefWitness(
|
||||
Context& context, SemIR::LocId loc_id,
|
||||
SemIR::ConstantId query_self_const_id,
|
||||
SemIR::SpecificInterfaceId query_specific_interface_id,
|
||||
const TypeStructure* best_impl_type_structure,
|
||||
SemIR::LocId best_impl_loc_id) -> SemIR::InstId {
|
||||
SemIR::SpecificInterfaceId query_specific_interface_id) -> SemIR::InstId {
|
||||
auto& clang_sema = context.clang_sema();
|
||||
|
||||
auto* class_decl = TypeAsClassDecl(context, query_self_const_id);
|
||||
@@ -168,8 +152,7 @@ static auto BuildCppUnsafeDerefWitness(
|
||||
}
|
||||
auto decl_info =
|
||||
DeclInfo{.decl = *candidates.begin(), .signature = {.num_params = 0}};
|
||||
auto fn_id = GetFunctionId(context, loc_id, decl_info,
|
||||
best_impl_type_structure, best_impl_loc_id);
|
||||
auto fn_id = GetFunctionId(context, loc_id, decl_info);
|
||||
if (fn_id == SemIR::ErrorInst::InstId || fn_id == SemIR::InstId::None) {
|
||||
return fn_id;
|
||||
}
|
||||
@@ -187,25 +170,79 @@ static auto BuildCppUnsafeDerefWitness(
|
||||
{result_type_id, fn_id});
|
||||
}
|
||||
|
||||
static auto BuildDefaultWitness(
|
||||
Context& context, SemIR::LocId loc_id,
|
||||
SemIR::ConstantId query_self_const_id,
|
||||
SemIR::SpecificInterfaceId query_specific_interface_id) -> SemIR::InstId {
|
||||
auto& clang_sema = context.clang_sema();
|
||||
|
||||
auto* class_decl = TypeAsClassDecl(context, query_self_const_id);
|
||||
if (!class_decl) {
|
||||
return SemIR::InstId::None;
|
||||
}
|
||||
// Clang would produce a warning for classes with uninitialized
|
||||
// [[clang::requires_init]] fields for which default initialization is
|
||||
// performed, and we don't have a good place to produce that warning.
|
||||
// That happens if class_decl->hasUninitializedExplicitInitFields() is true.
|
||||
//
|
||||
// TODO: Consider treating such types as not implementing `Default`.
|
||||
auto decl_info =
|
||||
DeclInfo{.decl = clang_sema.LookupDefaultConstructor(class_decl),
|
||||
.signature = {.num_params = 0}};
|
||||
auto fn_id = GetFunctionId(context, loc_id, decl_info);
|
||||
if (fn_id == SemIR::ErrorInst::InstId || fn_id == SemIR::InstId::None) {
|
||||
return fn_id;
|
||||
}
|
||||
return BuildCustomWitness(context, loc_id, query_self_const_id,
|
||||
query_specific_interface_id, {fn_id});
|
||||
}
|
||||
|
||||
static auto BuildDestroyWitness(
|
||||
Context& context, SemIR::LocId loc_id,
|
||||
SemIR::ConstantId query_self_const_id,
|
||||
SemIR::SpecificInterfaceId query_specific_interface_id) -> SemIR::InstId {
|
||||
auto& clang_sema = context.clang_sema();
|
||||
|
||||
// TODO: This should provide `Destroy` for enums and other trivially
|
||||
// destructible types.
|
||||
auto* class_decl = TypeAsClassDecl(context, query_self_const_id);
|
||||
if (!class_decl) {
|
||||
return SemIR::InstId::None;
|
||||
}
|
||||
auto decl_info = DeclInfo{.decl = clang_sema.LookupDestructor(class_decl),
|
||||
.signature = {.num_params = 0}};
|
||||
auto fn_id = GetFunctionId(context, loc_id, decl_info);
|
||||
if (fn_id == SemIR::ErrorInst::InstId || fn_id == SemIR::InstId::None) {
|
||||
return fn_id;
|
||||
}
|
||||
return BuildCustomWitness(context, loc_id, query_self_const_id,
|
||||
query_specific_interface_id, {fn_id});
|
||||
}
|
||||
|
||||
auto LookupCppImpl(Context& context, SemIR::LocId loc_id,
|
||||
CoreInterface core_interface,
|
||||
SemIR::ConstantId query_self_const_id,
|
||||
SemIR::SpecificInterfaceId query_specific_interface_id,
|
||||
const TypeStructure* best_impl_type_structure,
|
||||
SemIR::LocId best_impl_loc_id) -> SemIR::InstId {
|
||||
// TODO: Infer a C++ type structure and check whether it's less strict than
|
||||
// the best Carbon type structure.
|
||||
static_cast<void>(best_impl_type_structure);
|
||||
static_cast<void>(best_impl_loc_id);
|
||||
|
||||
switch (core_interface) {
|
||||
case CoreInterface::Copy:
|
||||
return BuildCopyWitness(context, loc_id, query_self_const_id,
|
||||
query_specific_interface_id,
|
||||
best_impl_type_structure, best_impl_loc_id);
|
||||
query_specific_interface_id);
|
||||
case CoreInterface::CppUnsafeDeref:
|
||||
return BuildCppUnsafeDerefWitness(context, loc_id, query_self_const_id,
|
||||
query_specific_interface_id);
|
||||
case CoreInterface::Default:
|
||||
return BuildDefaultWitness(context, loc_id, query_self_const_id,
|
||||
query_specific_interface_id);
|
||||
case CoreInterface::Destroy:
|
||||
return BuildDestroyWitness(context, loc_id, query_self_const_id,
|
||||
query_specific_interface_id,
|
||||
best_impl_type_structure, best_impl_loc_id);
|
||||
case CoreInterface::CppUnsafeDeref:
|
||||
return BuildCppUnsafeDerefWitness(
|
||||
context, loc_id, query_self_const_id, query_specific_interface_id,
|
||||
best_impl_type_structure, best_impl_loc_id);
|
||||
query_specific_interface_id);
|
||||
|
||||
// IntFitsIn is for Carbon integer types only.
|
||||
case CoreInterface::IntFitsIn:
|
||||
|
||||
@@ -64,6 +64,7 @@
|
||||
#include "toolchain/sem_ir/inst.h"
|
||||
#include "toolchain/sem_ir/name_scope.h"
|
||||
#include "toolchain/sem_ir/pattern.h"
|
||||
#include "toolchain/sem_ir/type_info.h"
|
||||
#include "toolchain/sem_ir/typed_insts.h"
|
||||
|
||||
namespace Carbon::Check {
|
||||
@@ -137,10 +138,12 @@ auto ImportCpp(Context& context,
|
||||
name_scope.set_is_closed_import(true);
|
||||
|
||||
if (GenerateAst(context, imports, fs, llvm_context, std::move(invocation))) {
|
||||
name_scope.set_clang_decl_context_id(context.clang_decls().Add(
|
||||
{.key = SemIR::ClangDeclKey(
|
||||
context.ast_context().getTranslationUnitDecl()),
|
||||
.inst_id = name_scope.inst_id()}));
|
||||
name_scope.set_clang_decl_context_id(
|
||||
context.clang_decls().Add(
|
||||
{.key = SemIR::ClangDeclKey(
|
||||
context.ast_context().getTranslationUnitDecl()),
|
||||
.inst_id = name_scope.inst_id()}),
|
||||
/*is_cpp_scope=*/true);
|
||||
} else {
|
||||
name_scope.set_has_error();
|
||||
}
|
||||
@@ -302,7 +305,8 @@ static auto ImportNamespaceDecl(Context& context,
|
||||
context.name_scopes()
|
||||
.Get(result.name_scope_id)
|
||||
.set_clang_decl_context_id(
|
||||
context.clang_decls().Add({.key = key, .inst_id = result.inst_id}));
|
||||
context.clang_decls().Add({.key = key, .inst_id = result.inst_id}),
|
||||
/*is_cpp_scope=*/true);
|
||||
return result.inst_id;
|
||||
}
|
||||
|
||||
@@ -318,8 +322,8 @@ static auto BuildClassDecl(Context& context,
|
||||
.class_id = SemIR::ClassId::None,
|
||||
.decl_block_id = SemIR::InstBlockId::None};
|
||||
auto class_decl_id = AddPlaceholderImportedInstInNoBlock(
|
||||
context,
|
||||
MakeImportedLocIdAndInst(context, import_ir_inst_id, class_decl));
|
||||
context, SemIR::LocIdAndInst::RuntimeVerified(
|
||||
context.sem_ir(), import_ir_inst_id, class_decl));
|
||||
|
||||
SemIR::Class class_info = {
|
||||
{.name_id = name_id,
|
||||
@@ -373,7 +377,7 @@ static auto ImportTagDecl(Context& context, clang::TagDecl* clang_decl)
|
||||
class_inst_id, SemIR::NameId::None, class_info.parent_scope_id);
|
||||
context.name_scopes()
|
||||
.Get(class_info.scope_id)
|
||||
.set_clang_decl_context_id(clang_decl_id);
|
||||
.set_clang_decl_context_id(clang_decl_id, /*is_cpp_scope=*/true);
|
||||
|
||||
return class_inst_id;
|
||||
}
|
||||
@@ -438,8 +442,8 @@ static auto ImportClassObjectRepr(Context& context, SemIR::ClassId class_id,
|
||||
clang_def->isAggregate()) {
|
||||
return context.types().GetAsTypeInstId(AddInst(
|
||||
context,
|
||||
MakeImportedLocIdAndInst(
|
||||
context, import_ir_inst_id,
|
||||
SemIR::LocIdAndInst::RuntimeVerified(
|
||||
context.sem_ir(), import_ir_inst_id,
|
||||
SemIR::StructType{.type_id = SemIR::TypeType::TypeId,
|
||||
.fields_id = SemIR::StructTypeFieldsId::Empty})));
|
||||
}
|
||||
@@ -447,14 +451,16 @@ static auto ImportClassObjectRepr(Context& context, SemIR::ClassId class_id,
|
||||
const auto& clang_layout =
|
||||
context.ast_context().getASTRecordLayout(clang_def);
|
||||
|
||||
llvm::SmallVector<uint64_t> layout;
|
||||
llvm::SmallVector<SemIR::ObjectSize> layout;
|
||||
llvm::SmallVector<SemIR::StructTypeField> fields;
|
||||
|
||||
static_assert(SemIR::CustomLayoutId::SizeIndex == 0);
|
||||
layout.push_back(clang_layout.getSize().getQuantity());
|
||||
layout.push_back(
|
||||
SemIR::ObjectSize::Bytes(clang_layout.getSize().getQuantity()));
|
||||
|
||||
static_assert(SemIR::CustomLayoutId::AlignIndex == 1);
|
||||
layout.push_back(clang_layout.getAlignment().getQuantity());
|
||||
layout.push_back(
|
||||
SemIR::ObjectSize::Bytes(clang_layout.getAlignment().getQuantity()));
|
||||
|
||||
static_assert(SemIR::CustomLayoutId::FirstFieldIndex == 2);
|
||||
|
||||
@@ -491,8 +497,8 @@ static auto ImportClassObjectRepr(Context& context, SemIR::ClassId class_id,
|
||||
|
||||
auto base_decl_id = AddInst(
|
||||
context,
|
||||
MakeImportedLocIdAndInst(
|
||||
context, import_ir_inst_id,
|
||||
SemIR::LocIdAndInst::RuntimeVerified(
|
||||
context.sem_ir(), import_ir_inst_id,
|
||||
SemIR::BaseDecl{.type_id = GetUnboundElementType(
|
||||
context, class_type_inst_id, base_type_inst_id),
|
||||
.base_type_inst_id = base_type_inst_id,
|
||||
@@ -526,7 +532,7 @@ static auto ImportClassObjectRepr(Context& context, SemIR::ClassId class_id,
|
||||
auto base_offset = base.isVirtual()
|
||||
? clang_layout.getVBaseClassOffset(base_class)
|
||||
: clang_layout.getBaseClassOffset(base_class);
|
||||
layout.push_back(base_offset.getQuantity());
|
||||
layout.push_back(SemIR::ObjectSize::Bytes(base_offset.getQuantity()));
|
||||
fields.push_back(
|
||||
{.name_id = SemIR::NameId::Base, .type_inst_id = base_type_inst_id});
|
||||
}
|
||||
@@ -574,8 +580,8 @@ static auto ImportClassObjectRepr(Context& context, SemIR::ClassId class_id,
|
||||
// Create a field now, as we know the index to use.
|
||||
// TODO: Consider doing this lazily instead.
|
||||
auto field_decl_id = AddInst(
|
||||
context, MakeImportedLocIdAndInst(
|
||||
context, import_ir_inst_id,
|
||||
context, SemIR::LocIdAndInst::RuntimeVerified(
|
||||
context.sem_ir(), import_ir_inst_id,
|
||||
SemIR::FieldDecl{
|
||||
.type_id = GetUnboundElementType(
|
||||
context, class_type_inst_id, field_type_inst_id),
|
||||
@@ -599,20 +605,20 @@ static auto ImportClassObjectRepr(Context& context, SemIR::ClassId class_id,
|
||||
offset += inner_layout.getFieldOffset(inner_field->getFieldIndex());
|
||||
}
|
||||
|
||||
layout.push_back(
|
||||
context.ast_context().toCharUnitsFromBits(offset).getQuantity());
|
||||
layout.push_back(SemIR::ObjectSize::Bits(offset));
|
||||
fields.push_back(
|
||||
{.name_id = field_name_id, .type_inst_id = field_type_inst_id});
|
||||
}
|
||||
|
||||
// TODO: Add a field to prevent tail padding reuse if necessary.
|
||||
|
||||
return AddTypeInst(context,
|
||||
MakeImportedLocIdAndInst<SemIR::CustomLayoutType>(
|
||||
context, import_ir_inst_id,
|
||||
{.type_id = SemIR::TypeType::TypeId,
|
||||
.fields_id = context.struct_type_fields().Add(fields),
|
||||
.layout_id = context.custom_layouts().Add(layout)}));
|
||||
return AddTypeInst(
|
||||
context, SemIR::LocIdAndInst::RuntimeVerified(
|
||||
context.sem_ir(), import_ir_inst_id,
|
||||
SemIR::CustomLayoutType{
|
||||
.type_id = SemIR::TypeType::TypeId,
|
||||
.fields_id = context.struct_type_fields().Add(fields),
|
||||
.layout_id = context.custom_layouts().Add(layout)}));
|
||||
}
|
||||
|
||||
// Creates a Carbon class definition based on the information in the given Clang
|
||||
@@ -633,12 +639,13 @@ static auto BuildClassDefinition(Context& context,
|
||||
// Compute the class's object representation.
|
||||
auto object_repr_id = ImportClassObjectRepr(
|
||||
context, class_id, import_ir_inst_id, class_inst_id, clang_def);
|
||||
class_info.complete_type_witness_id = AddInst(
|
||||
context,
|
||||
MakeImportedLocIdAndInst<SemIR::CompleteTypeWitness>(
|
||||
context, import_ir_inst_id,
|
||||
{.type_id = GetSingletonType(context, SemIR::WitnessType::TypeInstId),
|
||||
.object_repr_type_inst_id = object_repr_id}));
|
||||
class_info.complete_type_witness_id =
|
||||
AddInst(context, SemIR::LocIdAndInst::RuntimeVerified(
|
||||
context.sem_ir(), import_ir_inst_id,
|
||||
SemIR::CompleteTypeWitness{
|
||||
.type_id = GetSingletonType(
|
||||
context, SemIR::WitnessType::TypeInstId),
|
||||
.object_repr_type_inst_id = object_repr_id}));
|
||||
|
||||
class_info.body_block_id = context.inst_block_stack().Pop();
|
||||
}
|
||||
@@ -656,12 +663,13 @@ static auto ImportEnumObjectRepresentation(
|
||||
auto int_kind = int_type->isSignedIntegerType() ? SemIR::IntKind::Signed
|
||||
: SemIR::IntKind::Unsigned;
|
||||
auto bit_width_id = GetOrAddInst(
|
||||
context, MakeImportedLocIdAndInst<SemIR::IntValue>(
|
||||
context, import_ir_inst_id,
|
||||
{.type_id = GetSingletonType(
|
||||
context, SemIR::IntLiteralType::TypeInstId),
|
||||
.int_id = context.ints().AddUnsigned(llvm::APInt(
|
||||
64, context.ast_context().getIntWidth(int_type)))}));
|
||||
context, SemIR::LocIdAndInst::RuntimeVerified(
|
||||
context.sem_ir(), import_ir_inst_id,
|
||||
SemIR::IntValue{
|
||||
.type_id = GetSingletonType(
|
||||
context, SemIR::IntLiteralType::TypeInstId),
|
||||
.int_id = context.ints().AddUnsigned(llvm::APInt(
|
||||
64, context.ast_context().getIntWidth(int_type)))}));
|
||||
return context.types().GetAsTypeInstId(
|
||||
GetOrAddInst(context, SemIR::LocIdAndInst::NoLoc(SemIR::IntType{
|
||||
.type_id = SemIR::TypeType::TypeId,
|
||||
@@ -690,15 +698,16 @@ static auto BuildEnumDefinition(Context& context,
|
||||
auto object_repr_id =
|
||||
ImportEnumObjectRepresentation(context, import_ir_inst_id, enum_decl);
|
||||
class_info.adapt_id = AddInst(
|
||||
context, MakeImportedLocIdAndInst(
|
||||
context, import_ir_inst_id,
|
||||
context, SemIR::LocIdAndInst::RuntimeVerified(
|
||||
context.sem_ir(), import_ir_inst_id,
|
||||
SemIR::AdaptDecl{.adapted_type_inst_id = object_repr_id}));
|
||||
class_info.complete_type_witness_id = AddInst(
|
||||
context,
|
||||
MakeImportedLocIdAndInst<SemIR::CompleteTypeWitness>(
|
||||
context, import_ir_inst_id,
|
||||
{.type_id = GetSingletonType(context, SemIR::WitnessType::TypeInstId),
|
||||
.object_repr_type_inst_id = object_repr_id}));
|
||||
class_info.complete_type_witness_id =
|
||||
AddInst(context, SemIR::LocIdAndInst::RuntimeVerified(
|
||||
context.sem_ir(), import_ir_inst_id,
|
||||
SemIR::CompleteTypeWitness{
|
||||
.type_id = GetSingletonType(
|
||||
context, SemIR::WitnessType::TypeInstId),
|
||||
.object_repr_type_inst_id = object_repr_id}));
|
||||
|
||||
class_info.body_block_id = context.inst_block_stack().Pop();
|
||||
}
|
||||
@@ -721,9 +730,9 @@ static auto ImportEnumConstantDecl(Context& context,
|
||||
auto import_ir_inst_id =
|
||||
AddImportIRInst(context.sem_ir(), enumerator_decl->getLocation());
|
||||
auto inst_id = AddInstInNoBlock(
|
||||
context,
|
||||
MakeImportedLocIdAndInst<SemIR::IntValue>(
|
||||
context, import_ir_inst_id, {.type_id = type_id, .int_id = int_id}));
|
||||
context, SemIR::LocIdAndInst::RuntimeVerified(
|
||||
context.sem_ir(), import_ir_inst_id,
|
||||
SemIR::IntValue{.type_id = type_id, .int_id = int_id}));
|
||||
context.imports().push_back(inst_id);
|
||||
context.clang_decls().Add({.key = key, .inst_id = inst_id});
|
||||
return inst_id;
|
||||
@@ -1109,7 +1118,9 @@ static auto MakeImplicitParamPatternsBlockId(
|
||||
auto param_info = MapParameterType(context, loc_id, param_type);
|
||||
auto [type_inst_id, type_id] = param_info.type;
|
||||
SemIR::ExprRegionId type_expr_region_id =
|
||||
EndSubpatternAsExpr(context, type_inst_id);
|
||||
ConsumeSubpatternExpr(context, type_inst_id);
|
||||
|
||||
EndEmptySubpattern(context);
|
||||
|
||||
if (!type_id.has_value()) {
|
||||
context.TODO(loc_id,
|
||||
@@ -1159,7 +1170,7 @@ static auto MakeParamPatternsBlockId(Context& context, SemIR::LocId loc_id,
|
||||
ClangGetUnqualifiedTypePreserveNonNull(context, orig_param_type);
|
||||
|
||||
// Mark the start of a region of insts, needed for the type expression
|
||||
// created later with the call of `EndSubpatternAsExpr()`.
|
||||
// created later with the call of `ConsumeSubpatternExpr()`.
|
||||
BeginSubpattern(context);
|
||||
auto param_info = MapParameterType(context, loc_id, param_type);
|
||||
auto [type_inst_id, type_id] = param_info.type;
|
||||
@@ -1167,7 +1178,8 @@ static auto MakeParamPatternsBlockId(Context& context, SemIR::LocId loc_id,
|
||||
// region that allows control flow in the type expression e.g. fn F(x: if C
|
||||
// then i32 else i64).
|
||||
SemIR::ExprRegionId type_expr_region_id =
|
||||
EndSubpatternAsExpr(context, type_inst_id);
|
||||
ConsumeSubpatternExpr(context, type_inst_id);
|
||||
EndEmptySubpattern(context);
|
||||
|
||||
if (!type_id.has_value()) {
|
||||
context.TODO(loc_id, llvm::formatv("Unsupported: parameter type: {0}",
|
||||
@@ -1206,11 +1218,11 @@ static auto MakeParamPatternsBlockId(Context& context, SemIR::LocId loc_id,
|
||||
auto param_block_id = context.inst_blocks().Add(param_ids);
|
||||
auto tuple_pattern_type_id =
|
||||
GetPatternType(context, GetTupleType(context, param_type_ids));
|
||||
SemIR::InstId pattern_id = AddPatternInst(
|
||||
context,
|
||||
SemIR::LocIdAndInst::UncheckedLoc(
|
||||
loc_id, SemIR::TuplePattern{.type_id = tuple_pattern_type_id,
|
||||
.elements_id = param_block_id}));
|
||||
SemIR::InstId pattern_id = AddInst(
|
||||
context, SemIR::LocIdAndInst::RuntimeVerified(
|
||||
context.sem_ir(), loc_id,
|
||||
SemIR::TuplePattern{.type_id = tuple_pattern_type_id,
|
||||
.elements_id = param_block_id}));
|
||||
param_ids = {pattern_id};
|
||||
break;
|
||||
}
|
||||
@@ -1319,18 +1331,20 @@ static auto GetReturnInfo(Context& context, SemIR::LocId loc_id,
|
||||
auto return_patterns_id = SemIR::InstBlockId::Empty;
|
||||
if (auto init_form =
|
||||
context.insts().TryGetAs<SemIR::InitForm>(form_inst_id)) {
|
||||
SemIR::InstId return_slot_pattern_id = AddPatternInst(
|
||||
context, MakeImportedLocIdAndInst(
|
||||
context, return_type_import_ir_inst_id,
|
||||
SemIR::ReturnSlotPattern({.type_id = pattern_type_id,
|
||||
.type_inst_id = type_inst_id})));
|
||||
auto param_pattern_id = AddPatternInst(
|
||||
context,
|
||||
MakeImportedLocIdAndInst(
|
||||
context, return_type_import_ir_inst_id,
|
||||
SemIR::OutParamPattern({.type_id = pattern_type_id,
|
||||
.subpattern_id = return_slot_pattern_id})));
|
||||
return_patterns_id = context.inst_blocks().Add({param_pattern_id});
|
||||
auto param_pattern_id = AddInst(
|
||||
context, SemIR::LocIdAndInst::RuntimeVerified(
|
||||
context.sem_ir(), return_type_import_ir_inst_id,
|
||||
SemIR::OutParamPattern(
|
||||
{.type_id = pattern_type_id,
|
||||
.pretty_name_id = SemIR::NameId::ReturnSlot})));
|
||||
SemIR::InstId return_slot_pattern_id =
|
||||
AddInst(context,
|
||||
SemIR::LocIdAndInst::RuntimeVerified(
|
||||
context.sem_ir(), return_type_import_ir_inst_id,
|
||||
SemIR::ReturnSlotPattern({.type_id = pattern_type_id,
|
||||
.subpattern_id = param_pattern_id,
|
||||
.type_inst_id = type_inst_id})));
|
||||
return_patterns_id = context.inst_blocks().Add({return_slot_pattern_id});
|
||||
}
|
||||
return {.return_type_inst_id = type_inst_id,
|
||||
.return_form_inst_id = form_inst_id,
|
||||
@@ -1578,8 +1592,10 @@ static auto ImportFunctionDecl(Context& context, SemIR::LocId loc_id,
|
||||
context, loc_id, import_ir_inst_id, thunk_clang_decl,
|
||||
{.num_params =
|
||||
static_cast<int32_t>(thunk_clang_decl->getNumParams())})) {
|
||||
auto& thunk_function = context.functions().Get(*thunk_function_id);
|
||||
thunk_function.SetCppThunk(function_info.first_owning_decl_id);
|
||||
SemIR::InstId thunk_function_decl_id =
|
||||
context.functions().Get(*thunk_function_id).first_owning_decl_id;
|
||||
thunk_function.first_owning_decl_id;
|
||||
function_info.SetHasCppThunk(thunk_function_decl_id);
|
||||
}
|
||||
}
|
||||
@@ -1771,7 +1787,8 @@ static auto ImportTemplateDecl(Context& context,
|
||||
SemIR::StructValue value = {.type_id = SemIR::TypeId::None,
|
||||
.elements_id = SemIR::InstBlockId::Empty};
|
||||
auto inst_id = AddPlaceholderImportedInstInNoBlock(
|
||||
context, MakeImportedLocIdAndInst(context, import_loc_id, value));
|
||||
context, SemIR::LocIdAndInst::RuntimeVerified(context.sem_ir(),
|
||||
import_loc_id, value));
|
||||
|
||||
// Create a type for the constant value.
|
||||
auto name_id = context.entity_names().Add(
|
||||
|
||||
@@ -26,7 +26,7 @@ static auto GetFileInfo(Context& context, SemIR::CheckIRId ir_id) -> FileInfo {
|
||||
if (ir_id != context.sem_ir().check_ir_id()) {
|
||||
auto import_id = context.check_ir_map().Get(ir_id);
|
||||
CARBON_CHECK(import_id.has_value());
|
||||
file_index = import_id.index + 1;
|
||||
file_index = context.import_irs().GetRawIndex(import_id) + 1;
|
||||
|
||||
sem_ir = context.import_irs().Get(import_id).sem_ir;
|
||||
CARBON_CHECK(sem_ir, "Node location in nonexistent IR");
|
||||
|
||||
@@ -247,11 +247,12 @@ static auto MakeCppStdInitializerListMake(Context& context, SemIR::LocId loc_id,
|
||||
context, SemIR::IntLiteralType::TypeInstId),
|
||||
.int_id = context.ints().Add(size)}));
|
||||
auto array_type_inst_id = AddTypeInst(
|
||||
context, SemIR::LocIdAndInst::UncheckedLoc(
|
||||
loc_id, SemIR::ArrayType{
|
||||
.type_id = SemIR::TypeType::TypeId,
|
||||
.bound_id = bound_id,
|
||||
.element_type_inst_id = element_type_inst_id}));
|
||||
context,
|
||||
SemIR::LocIdAndInst::RuntimeVerified(
|
||||
context.sem_ir(), loc_id,
|
||||
SemIR::ArrayType{.type_id = SemIR::TypeType::TypeId,
|
||||
.bound_id = bound_id,
|
||||
.element_type_inst_id = element_type_inst_id}));
|
||||
auto array_type_id =
|
||||
context.types().GetTypeIdForTypeInstId(array_type_inst_id);
|
||||
|
||||
|
||||
@@ -419,19 +419,20 @@ static auto CreateThunkFunctionDecl(
|
||||
thunk_param_types, ext_proto_info);
|
||||
|
||||
clang::DeclContext* decl_context = ast_context.getTranslationUnitDecl();
|
||||
// TODO: Thunks should not have external linkage, consider using `SC_Static`.
|
||||
clang::FunctionDecl* thunk_function_decl =
|
||||
clang::FunctionDecl::Create(ast_context, decl_context, clang_loc,
|
||||
clang_loc, name, thunk_function_type,
|
||||
/*TInfo=*/nullptr, clang::SC_Extern);
|
||||
clang::FunctionDecl* thunk_function_decl = clang::FunctionDecl::Create(
|
||||
ast_context, decl_context, clang_loc, clang_loc, name,
|
||||
thunk_function_type, /*TInfo=*/nullptr, clang::SC_None,
|
||||
/*UsesFPIntrin=*/false, /*isInlineSpecified=*/true);
|
||||
decl_context->addDecl(thunk_function_decl);
|
||||
|
||||
thunk_function_decl->setParams(
|
||||
BuildThunkParameters(ast_context, callee_info, thunk_function_decl));
|
||||
|
||||
// Set always_inline.
|
||||
// Force the thunk to be inlined and discarded.
|
||||
thunk_function_decl->addAttr(
|
||||
clang::AlwaysInlineAttr::CreateImplicit(ast_context));
|
||||
thunk_function_decl->addAttr(
|
||||
clang::InternalLinkageAttr::CreateImplicit(ast_context));
|
||||
|
||||
// Set asm("<callee function mangled name>.carbon_thunk").
|
||||
thunk_function_decl->addAttr(clang::AsmLabelAttr::CreateImplicit(
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "toolchain/base/value_ids.h"
|
||||
#include "toolchain/check/context.h"
|
||||
#include "toolchain/check/convert.h"
|
||||
#include "toolchain/check/cpp/export.h"
|
||||
#include "toolchain/check/cpp/import.h"
|
||||
#include "toolchain/check/cpp/location.h"
|
||||
#include "toolchain/check/literal.h"
|
||||
@@ -65,8 +66,8 @@ static auto FindIntLiteralBitWidth(Context& context, SemIR::LocId loc_id,
|
||||
// TODO: Add tests for these cases.
|
||||
return IntId::None;
|
||||
}
|
||||
auto arg = context.insts().TryGetAs<SemIR::IntValue>(
|
||||
context.constant_values().GetInstId(arg_const_id));
|
||||
auto arg =
|
||||
context.constant_values().TryGetInstAs<SemIR::IntValue>(arg_const_id);
|
||||
if (!arg) {
|
||||
return IntId::None;
|
||||
}
|
||||
@@ -137,21 +138,10 @@ static auto VerifyIntegerTypeWidth(Context& context, clang::QualType type,
|
||||
|
||||
// Maps a Carbon class type to a C++ type. Returns a null `QualType` if the
|
||||
// type is not supported.
|
||||
static auto TryMapClassType(Context& context, SemIR::ClassType class_type)
|
||||
-> TryMapTypeResult {
|
||||
static auto TryMapClassType(Context& context, SemIR::TypeInstId class_inst_id,
|
||||
SemIR::ClassType class_type) -> TryMapTypeResult {
|
||||
clang::ASTContext& ast_context = context.ast_context();
|
||||
|
||||
// If the class was imported from C++, return the original C++ type.
|
||||
auto clang_decl_id =
|
||||
context.name_scopes()
|
||||
.Get(context.classes().Get(class_type.class_id).scope_id)
|
||||
.clang_decl_context_id();
|
||||
if (clang_decl_id.has_value()) {
|
||||
clang::Decl* clang_decl = context.clang_decls().Get(clang_decl_id).key.decl;
|
||||
auto* tag_type_decl = clang::cast<clang::TagDecl>(clang_decl);
|
||||
return ast_context.getCanonicalTagType(tag_type_decl);
|
||||
}
|
||||
|
||||
// If the class represents a Carbon type literal, map it to the corresponding
|
||||
// C++ builtin type.
|
||||
auto type_info =
|
||||
@@ -230,10 +220,18 @@ static auto TryMapClassType(Context& context, SemIR::ClassType class_type)
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise we don't have a mapping for this Carbon class type.
|
||||
// TODO: If the class type wasn't imported from C++, create a corresponding
|
||||
// C++ class type.
|
||||
return clang::QualType();
|
||||
// TODO: We cannot yet map specific classes.
|
||||
if (class_type.specific_id.has_value()) {
|
||||
return clang::QualType();
|
||||
}
|
||||
|
||||
// Otherwise, find the existing C++ declaration or create a new one.
|
||||
auto* tag_decl = ExportClassToCpp(context, SemIR::LocId(class_inst_id),
|
||||
class_inst_id, class_type);
|
||||
if (!tag_decl) {
|
||||
return clang::QualType();
|
||||
}
|
||||
return ast_context.getCanonicalTagType(tag_decl);
|
||||
}
|
||||
|
||||
// Maps a Carbon type to a C++ type. Either returns the mapped type, a null type
|
||||
@@ -253,7 +251,8 @@ static auto TryMapType(Context& context, SemIR::TypeId type_id)
|
||||
return context.ast_context().CharTy;
|
||||
}
|
||||
case CARBON_KIND(SemIR::ClassType class_type): {
|
||||
return TryMapClassType(context, class_type);
|
||||
return TryMapClassType(context, context.types().GetTypeInstId(type_id),
|
||||
class_type);
|
||||
}
|
||||
case CARBON_KIND(SemIR::ConstType const_type): {
|
||||
return WrappedType{
|
||||
|
||||
+311
-117
@@ -18,6 +18,7 @@
|
||||
#include "toolchain/sem_ir/associated_constant.h"
|
||||
#include "toolchain/sem_ir/builtin_function_kind.h"
|
||||
#include "toolchain/sem_ir/ids.h"
|
||||
#include "toolchain/sem_ir/type_info.h"
|
||||
#include "toolchain/sem_ir/typed_insts.h"
|
||||
|
||||
namespace Carbon::Check {
|
||||
@@ -40,56 +41,290 @@ static auto GetFacetAsType(Context& context,
|
||||
return context.types().GetTypeIdForTypeInstId(facet_or_type_id);
|
||||
}
|
||||
|
||||
// Returns a manufactured `Copy.Op` function with the `self` parameter typed
|
||||
// to `self_type_id`.
|
||||
static auto MakeCopyOpFunction(Context& context, SemIR::LocId loc_id,
|
||||
SemIR::TypeId self_type_id,
|
||||
SemIR::NameScopeId parent_scope_id)
|
||||
-> SemIR::InstId {
|
||||
auto name_id = context.core_identifiers().AddNameId(CoreIdentifier::Op);
|
||||
|
||||
auto [decl_id, function_id] =
|
||||
MakeGeneratedFunctionDecl(context, loc_id,
|
||||
{.parent_scope_id = parent_scope_id,
|
||||
.name_id = name_id,
|
||||
.self_type_id = self_type_id,
|
||||
.self_is_ref = false,
|
||||
.return_type_id = self_type_id});
|
||||
|
||||
auto& function = context.functions().Get(function_id);
|
||||
function.SetCoreWitness(SemIR::BuiltinFunctionKind::PrimitiveCopy);
|
||||
|
||||
return decl_id;
|
||||
}
|
||||
|
||||
// Returns the body for `Destroy.Op`. This will return `None` if using the
|
||||
// builtin `NoOp` is appropriate.
|
||||
// Returns a FacetType that contains only the query interface.
|
||||
static auto GetFacetTypeForQuerySpecificInterface(
|
||||
Context& context, SemIR::LocId loc_id,
|
||||
SemIR::SpecificInterfaceId query_specific_interface_id)
|
||||
-> SemIR::ConstantId {
|
||||
const auto query_specific_interface =
|
||||
context.specific_interfaces().Get(query_specific_interface_id);
|
||||
|
||||
// The Self facet will have type FacetType, for the query interface.
|
||||
auto const_id = EvalOrAddInst<SemIR::FacetType>(
|
||||
context, loc_id,
|
||||
FacetTypeFromInterface(context, query_specific_interface.interface_id,
|
||||
query_specific_interface.specific_id));
|
||||
return const_id;
|
||||
}
|
||||
|
||||
// Starts a block for lookup-related instructions, and returns the `FacetType`
|
||||
// for lookups in `HasWitnessForRepeatedField`.
|
||||
static auto PrepareForHasWitness(
|
||||
Context& context, SemIR::LocId loc_id,
|
||||
SemIR::SpecificInterfaceId query_specific_interface_id)
|
||||
-> SemIR::ConstantId {
|
||||
context.inst_block_stack().Push();
|
||||
StartGenericDecl(context);
|
||||
|
||||
return GetFacetTypeForQuerySpecificInterface(context, loc_id,
|
||||
query_specific_interface_id);
|
||||
}
|
||||
|
||||
// Cleans up state `PrepareForHasWitness`.
|
||||
static auto CleanupAfterHasWitness(Context& context) -> void {
|
||||
DiscardGenericDecl(context);
|
||||
context.inst_block_stack().PopAndDiscard();
|
||||
}
|
||||
|
||||
// Returns true if `type_inst_id` has a witness for the query interface, which
|
||||
// comes from `PrepareForHasWitness`.
|
||||
static auto HasWitnessForRepeatedField(
|
||||
Context& context, SemIR::LocId loc_id, SemIR::InstId type_inst_id,
|
||||
SemIR::ConstantId query_facet_type_const_id) -> bool {
|
||||
auto type_const_id = context.constant_values().Get(type_inst_id);
|
||||
auto block_or_err = LookupImplWitness(context, loc_id, type_const_id,
|
||||
query_facet_type_const_id);
|
||||
return block_or_err.has_value();
|
||||
}
|
||||
|
||||
// The format for `Destroy.Op`.
|
||||
enum class DestroyFormat {
|
||||
NoDestroy,
|
||||
Trivial,
|
||||
NonTrivial,
|
||||
};
|
||||
|
||||
// Similar to `HasWitnessForRepeatedField`, but for cases where there's only one
|
||||
// field, this can handle the call to `PrepareForHasWitness`.
|
||||
static auto HasWitnessForOneField(
|
||||
Context& context, SemIR::LocId loc_id, SemIR::InstId field_inst_id,
|
||||
SemIR::SpecificInterfaceId query_specific_interface_id) -> DestroyFormat {
|
||||
auto query_facet_type_const_id =
|
||||
PrepareForHasWitness(context, loc_id, query_specific_interface_id);
|
||||
auto has_witness = HasWitnessForRepeatedField(context, loc_id, field_inst_id,
|
||||
query_facet_type_const_id);
|
||||
CleanupAfterHasWitness(context);
|
||||
return has_witness ? DestroyFormat::NonTrivial : DestroyFormat::NoDestroy;
|
||||
}
|
||||
|
||||
// Returns true if `class_type` should impl `Destroy`.
|
||||
static auto CanDestroyClass(
|
||||
Context& context, SemIR::LocId loc_id, SemIR::ClassType class_type,
|
||||
const SemIR::CompleteTypeInfo& complete_info,
|
||||
SemIR::SpecificInterfaceId query_specific_interface_id, bool is_partial)
|
||||
-> DestroyFormat {
|
||||
// Abstract classes can't be destroyed.
|
||||
if (!is_partial && complete_info.IsAbstract()) {
|
||||
return DestroyFormat::NoDestroy;
|
||||
}
|
||||
|
||||
auto class_info = context.classes().Get(class_type.class_id);
|
||||
|
||||
// `LookupCppImpl` handles C++ types.
|
||||
if (context.name_scopes().Get(class_info.scope_id).is_cpp_scope()) {
|
||||
return DestroyFormat::NoDestroy;
|
||||
}
|
||||
|
||||
auto object_repr_id =
|
||||
class_info.GetObjectRepr(context.sem_ir(), class_type.specific_id);
|
||||
return HasWitnessForOneField(context, loc_id,
|
||||
context.types().GetTypeInstId(object_repr_id),
|
||||
query_specific_interface_id);
|
||||
}
|
||||
|
||||
// Returns true if the `Self` should impl `Destroy`. This will recurse into impl
|
||||
// lookup of `Destroy` for members, similar to `where .Self.members each impls
|
||||
// Destroy`.
|
||||
static auto CanDestroyType(
|
||||
Context& context, SemIR::LocId loc_id,
|
||||
SemIR::ConstantId query_self_const_id,
|
||||
SemIR::SpecificInterfaceId query_specific_interface_id) -> DestroyFormat {
|
||||
auto query_specific_interface =
|
||||
context.specific_interfaces().Get(query_specific_interface_id);
|
||||
auto destroy_interface_id = query_specific_interface.interface_id;
|
||||
|
||||
auto inst_id = context.constant_values().GetInstId(
|
||||
GetCanonicalFacetOrTypeValue(context, query_self_const_id));
|
||||
auto inst = context.insts().Get(inst_id);
|
||||
|
||||
// For facet values, look if the FacetType provides the same.
|
||||
if (auto facet_type =
|
||||
context.types().TryGetAs<SemIR::FacetType>(inst.type_id())) {
|
||||
const auto& info = context.facet_types().Get(facet_type->facet_type_id);
|
||||
for (auto interface : info.extend_constraints) {
|
||||
if (interface.interface_id == destroy_interface_id) {
|
||||
return DestroyFormat::Trivial;
|
||||
}
|
||||
}
|
||||
return DestroyFormat::NoDestroy;
|
||||
}
|
||||
|
||||
// Incomplete types can not be destroyed.
|
||||
auto type_id = context.types().GetTypeIdForTypeInstId(inst_id);
|
||||
if (!TryToCompleteType(context, type_id, loc_id)) {
|
||||
return DestroyFormat::NoDestroy;
|
||||
}
|
||||
|
||||
CARBON_KIND_SWITCH(inst) {
|
||||
case SemIR::ImplWitnessAccess::Kind:
|
||||
case SemIR::SymbolicBinding::Kind: {
|
||||
// A symbolic facet of type `type`. Such symbolic values can't be
|
||||
// destroyed.
|
||||
return DestroyFormat::NoDestroy;
|
||||
}
|
||||
|
||||
case CARBON_KIND(SemIR::ArrayType array_type): {
|
||||
// A zero element array is always trivially destructible.
|
||||
if (auto int_bound =
|
||||
context.sem_ir().GetZExtIntValue(array_type.bound_id);
|
||||
!int_bound || *int_bound == 0) {
|
||||
return DestroyFormat::Trivial;
|
||||
}
|
||||
|
||||
// Verify the element can be destroyed.
|
||||
return HasWitnessForOneField(context, loc_id,
|
||||
array_type.element_type_inst_id,
|
||||
query_specific_interface_id);
|
||||
}
|
||||
|
||||
case SemIR::Call::Kind:
|
||||
// TODO: These seem like they shouldn't be getting directly queried for
|
||||
// destroy. The use is in a test that was TODO before this TODO.
|
||||
return DestroyFormat::NoDestroy;
|
||||
|
||||
case CARBON_KIND(SemIR::ClassType class_type): {
|
||||
return CanDestroyClass(context, loc_id, class_type,
|
||||
context.types().GetCompleteTypeInfo(type_id),
|
||||
query_specific_interface_id,
|
||||
/*is_partial=*/false);
|
||||
}
|
||||
|
||||
case CARBON_KIND(SemIR::ConstType const_type): {
|
||||
return HasWitnessForOneField(context, loc_id, const_type.inner_id,
|
||||
query_specific_interface_id);
|
||||
}
|
||||
|
||||
case CARBON_KIND(SemIR::MaybeUnformedType maybe_unformed_type): {
|
||||
return HasWitnessForOneField(context, loc_id,
|
||||
maybe_unformed_type.inner_id,
|
||||
query_specific_interface_id);
|
||||
}
|
||||
|
||||
case CARBON_KIND(SemIR::PartialType partial_type): {
|
||||
// In contrast with something like `const`, need to treat the inner
|
||||
// class differently based on the `partial` modifier.
|
||||
auto class_type =
|
||||
context.insts().GetAs<SemIR::ClassType>(partial_type.inner_id);
|
||||
return CanDestroyClass(context, loc_id, class_type,
|
||||
context.types().GetCompleteTypeInfo(type_id),
|
||||
query_specific_interface_id,
|
||||
/*is_partial=*/true);
|
||||
}
|
||||
|
||||
case CARBON_KIND(SemIR::StructType struct_type): {
|
||||
auto fields = context.struct_type_fields().Get(struct_type.fields_id);
|
||||
if (fields.empty()) {
|
||||
return DestroyFormat::Trivial;
|
||||
}
|
||||
auto query_facet_type_const_id =
|
||||
PrepareForHasWitness(context, loc_id, query_specific_interface_id);
|
||||
bool has_witness = true;
|
||||
for (const auto& field : fields) {
|
||||
if (!HasWitnessForRepeatedField(context, loc_id, field.type_inst_id,
|
||||
query_facet_type_const_id)) {
|
||||
has_witness = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
CleanupAfterHasWitness(context);
|
||||
return has_witness ? DestroyFormat::NonTrivial : DestroyFormat::NoDestroy;
|
||||
}
|
||||
|
||||
case CARBON_KIND(SemIR::TupleType tuple_type): {
|
||||
auto block = context.inst_blocks().Get(tuple_type.type_elements_id);
|
||||
if (block.empty()) {
|
||||
return DestroyFormat::Trivial;
|
||||
}
|
||||
auto query_facet_type_const_id =
|
||||
PrepareForHasWitness(context, loc_id, query_specific_interface_id);
|
||||
bool has_witness = true;
|
||||
for (const auto& element_id : block) {
|
||||
if (!HasWitnessForRepeatedField(context, loc_id, element_id,
|
||||
query_facet_type_const_id)) {
|
||||
has_witness = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
CleanupAfterHasWitness(context);
|
||||
return has_witness ? DestroyFormat::NonTrivial : DestroyFormat::NoDestroy;
|
||||
}
|
||||
|
||||
case SemIR::BoolType::Kind:
|
||||
case SemIR::FacetType::Kind:
|
||||
case SemIR::FloatType::Kind:
|
||||
case SemIR::IntLiteralType::Kind:
|
||||
case SemIR::IntType::Kind:
|
||||
case SemIR::PointerType::Kind:
|
||||
case SemIR::TypeType::Kind:
|
||||
// Trivially destructible.
|
||||
return DestroyFormat::Trivial;
|
||||
|
||||
default:
|
||||
CARBON_FATAL("Unexpected type for CanDestroyType: {0}", inst.kind());
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the body for `Destroy.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`.
|
||||
// TODO: This mirrors `TypeCanDestroy` below, think about ways to share what's
|
||||
// handled.
|
||||
static auto MakeDestroyOpBody(Context& context, SemIR::LocId loc_id,
|
||||
SemIR::TypeId self_type_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);
|
||||
|
||||
while (auto class_type = inst.TryAs<SemIR::ClassType>()) {
|
||||
// Switch to looking at the object representation.
|
||||
auto class_info = context.classes().Get(class_type->class_id);
|
||||
CARBON_CHECK(class_info.is_complete());
|
||||
inst = context.types().GetAsInst(
|
||||
class_info.GetObjectRepr(context.sem_ir(), class_type->specific_id));
|
||||
}
|
||||
|
||||
CARBON_KIND_SWITCH(inst) {
|
||||
case SemIR::ArrayType::Kind:
|
||||
case SemIR::ClassType::Kind:
|
||||
case SemIR::ConstType::Kind:
|
||||
case SemIR::MaybeUnformedType::Kind:
|
||||
case SemIR::PartialType::Kind:
|
||||
case SemIR::StructType::Kind:
|
||||
case SemIR::TupleType::Kind:
|
||||
// TODO: Implement iterative destruction of types.
|
||||
break;
|
||||
case SemIR::BoolType::Kind:
|
||||
case SemIR::FloatType::Kind:
|
||||
case SemIR::IntType::Kind:
|
||||
case SemIR::PointerType::Kind:
|
||||
// For trivially destructible types, we don't generate anything, so that
|
||||
// this can collapse to a noop implementation when possible.
|
||||
break;
|
||||
case SemIR::ErrorInst::Kind:
|
||||
// Errors can't be destroyed, but we'll still try to generate calls for
|
||||
// other members.
|
||||
(void)self_param_id;
|
||||
// TODO: Implement destruction of the type.
|
||||
break;
|
||||
default:
|
||||
CARBON_FATAL("Unexpected type for destroy: {0}", inst);
|
||||
CARBON_FATAL("Unexpected type for MakeDestroyOpBody: {0}", inst);
|
||||
}
|
||||
|
||||
if (context.inst_block_stack().PeekCurrentBlockContents().empty()) {
|
||||
context.inst_block_stack().PopAndDiscard();
|
||||
return SemIR::InstBlockId::None;
|
||||
}
|
||||
AddInst(context, loc_id, SemIR::Return{});
|
||||
return context.inst_block_stack().Pop();
|
||||
}
|
||||
@@ -98,8 +333,8 @@ static auto MakeDestroyOpBody(Context& context, SemIR::LocId loc_id,
|
||||
// to `self_type_id`.
|
||||
static auto MakeDestroyOpFunction(Context& context, SemIR::LocId loc_id,
|
||||
SemIR::TypeId self_type_id,
|
||||
SemIR::NameScopeId parent_scope_id)
|
||||
-> SemIR::InstId {
|
||||
SemIR::NameScopeId parent_scope_id,
|
||||
DestroyFormat format) -> SemIR::InstId {
|
||||
auto name_id = context.core_identifiers().AddNameId(CoreIdentifier::Op);
|
||||
|
||||
auto [decl_id, function_id] =
|
||||
@@ -110,12 +345,14 @@ static auto MakeDestroyOpFunction(Context& context, SemIR::LocId loc_id,
|
||||
|
||||
auto& function = context.functions().Get(function_id);
|
||||
|
||||
auto body_id = MakeDestroyOpBody(context, loc_id, self_type_id);
|
||||
if (body_id.has_value()) {
|
||||
function.SetCoreWitness();
|
||||
function.body_block_ids.push_back(body_id);
|
||||
} else {
|
||||
if (format == DestroyFormat::Trivial) {
|
||||
function.SetCoreWitness(SemIR::BuiltinFunctionKind::NoOp);
|
||||
} else {
|
||||
CARBON_CHECK(format == DestroyFormat::NonTrivial);
|
||||
function.SetCoreWitness(SemIR::BuiltinFunctionKind::None);
|
||||
auto body_id = MakeDestroyOpBody(context, loc_id, self_type_id,
|
||||
function.self_param_id);
|
||||
function.body_block_ids.push_back(body_id);
|
||||
}
|
||||
|
||||
return decl_id;
|
||||
@@ -147,17 +384,11 @@ static auto GetTypesForSelfFacet(
|
||||
SemIR::ConstantId query_self_const_id,
|
||||
SemIR::SpecificInterfaceId query_specific_interface_id)
|
||||
-> TypesForSelfFacet {
|
||||
const auto query_specific_interface =
|
||||
context.specific_interfaces().Get(query_specific_interface_id);
|
||||
|
||||
// The Self facet will have type FacetType, for the query interface.
|
||||
auto facet_type_for_query_specific_interface =
|
||||
context.types().GetTypeIdForTypeConstantId(
|
||||
EvalOrAddInst<SemIR::FacetType>(
|
||||
context, loc_id,
|
||||
FacetTypeFromInterface(context,
|
||||
query_specific_interface.interface_id,
|
||||
query_specific_interface.specific_id)));
|
||||
GetFacetTypeForQuerySpecificInterface(context, loc_id,
|
||||
query_specific_interface_id));
|
||||
// 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);
|
||||
@@ -226,6 +457,8 @@ auto BuildCustomWitness(Context& context, SemIR::LocId loc_id,
|
||||
auto interface_with_self_specific_id = MakeSpecificWithInnerSelf(
|
||||
context, loc_id, interface.generic_id, interface.generic_with_self_id,
|
||||
query_specific_interface.specific_id, self_facet);
|
||||
CARBON_CHECK(
|
||||
!context.specifics().Get(interface_with_self_specific_id).HasError());
|
||||
|
||||
auto decl_id =
|
||||
context.constant_values().GetInstId(SemIR::GetConstantValueInSpecific(
|
||||
@@ -304,9 +537,10 @@ auto GetCoreInterface(Context& context, SemIR::InterfaceId interface_id)
|
||||
|
||||
constexpr auto CoreIdentifiersToInterfaces = std::array{
|
||||
std::pair{CoreIdentifier::Copy, CoreInterface::Copy},
|
||||
std::pair{CoreIdentifier::CppUnsafeDeref, CoreInterface::CppUnsafeDeref},
|
||||
std::pair{CoreIdentifier::Default, CoreInterface::Default},
|
||||
std::pair{CoreIdentifier::Destroy, CoreInterface::Destroy},
|
||||
std::pair{CoreIdentifier::IntFitsIn, CoreInterface::IntFitsIn},
|
||||
std::pair{CoreIdentifier::CppUnsafeDeref, CoreInterface::CppUnsafeDeref}};
|
||||
std::pair{CoreIdentifier::IntFitsIn, CoreInterface::IntFitsIn}};
|
||||
|
||||
for (auto [core_identifier, core_interface] : CoreIdentifiersToInterfaces) {
|
||||
if (interface.name_id ==
|
||||
@@ -317,88 +551,43 @@ auto GetCoreInterface(Context& context, SemIR::InterfaceId interface_id)
|
||||
return CoreInterface::Unknown;
|
||||
}
|
||||
|
||||
// Returns true if the `Self` should impl `Destroy`.
|
||||
static auto TypeCanDestroy(Context& context,
|
||||
SemIR::ConstantId query_self_const_id,
|
||||
SemIR::InterfaceId destroy_interface_id) -> bool {
|
||||
auto inst = context.insts().Get(context.constant_values().GetInstId(
|
||||
GetCanonicalFacetOrTypeValue(context, query_self_const_id)));
|
||||
|
||||
// For facet values, look if the FacetType provides the same.
|
||||
if (auto facet_type =
|
||||
context.types().TryGetAs<SemIR::FacetType>(inst.type_id())) {
|
||||
const auto& info = context.facet_types().Get(facet_type->facet_type_id);
|
||||
for (auto interface : info.extend_constraints) {
|
||||
if (interface.interface_id == destroy_interface_id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CARBON_KIND_SWITCH(inst) {
|
||||
case CARBON_KIND(SemIR::ClassType class_type): {
|
||||
auto class_info = context.classes().Get(class_type.class_id);
|
||||
// Incomplete and abstract classes can't be destroyed.
|
||||
if (!class_info.is_complete() ||
|
||||
class_info.inheritance_kind ==
|
||||
SemIR::Class::InheritanceKind::Abstract) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// `LookupCppImpl` handles C++ types.
|
||||
if (context.name_scopes().Get(class_info.scope_id).is_cpp_scope()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Return false if the object repr doesn't impl `Destroy`.
|
||||
return true;
|
||||
}
|
||||
case SemIR::ArrayType::Kind:
|
||||
case SemIR::ConstType::Kind:
|
||||
case SemIR::MaybeUnformedType::Kind:
|
||||
case SemIR::PartialType::Kind:
|
||||
case SemIR::StructType::Kind:
|
||||
case SemIR::TupleType::Kind:
|
||||
// TODO: Return false for types that indirectly reference a type that
|
||||
// doesn't impl `Destroy`.
|
||||
return true;
|
||||
case SemIR::BoolType::Kind:
|
||||
case SemIR::FloatType::Kind:
|
||||
case SemIR::IntType::Kind:
|
||||
case SemIR::PointerType::Kind:
|
||||
// Trivially destructible.
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
auto BuildPrimitiveCopyWitness(
|
||||
Context& context, SemIR::LocId loc_id, SemIR::NameScopeId parent_scope_id,
|
||||
SemIR::ConstantId query_self_const_id,
|
||||
SemIR::SpecificInterfaceId query_specific_interface_id) -> SemIR::InstId {
|
||||
auto self_type_id = GetFacetAsType(context, query_self_const_id);
|
||||
auto op_id =
|
||||
MakeCopyOpFunction(context, loc_id, self_type_id, parent_scope_id);
|
||||
return BuildCustomWitness(context, loc_id, query_self_const_id,
|
||||
query_specific_interface_id, {op_id});
|
||||
}
|
||||
|
||||
static auto MakeDestroyWitness(
|
||||
Context& context, SemIR::LocId loc_id,
|
||||
SemIR::ConstantId query_self_const_id,
|
||||
SemIR::SpecificInterfaceId query_specific_interface_id)
|
||||
SemIR::SpecificInterfaceId query_specific_interface_id, bool build_witness)
|
||||
-> std::optional<SemIR::InstId> {
|
||||
auto query_specific_interface =
|
||||
context.specific_interfaces().Get(query_specific_interface_id);
|
||||
|
||||
if (!TypeCanDestroy(context, query_self_const_id,
|
||||
query_specific_interface.interface_id)) {
|
||||
auto format = CanDestroyType(context, loc_id, query_self_const_id,
|
||||
query_specific_interface_id);
|
||||
if (format == DestroyFormat::NoDestroy) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (query_self_const_id.is_symbolic()) {
|
||||
if (!build_witness || query_self_const_id.is_symbolic()) {
|
||||
// The type can be destroyed, but we shouldn't make a witness right now.
|
||||
return SemIR::InstId::None;
|
||||
}
|
||||
|
||||
// Mark functions with the interface's scope as a hint to mangling. This does
|
||||
// not add them to the scope.
|
||||
// Mark functions with the interface's scope as a hint to mangling. This
|
||||
// does not add them to the scope.
|
||||
auto query_specific_interface =
|
||||
context.specific_interfaces().Get(query_specific_interface_id);
|
||||
auto parent_scope_id = context.interfaces()
|
||||
.Get(query_specific_interface.interface_id)
|
||||
.scope_without_self_id;
|
||||
|
||||
auto self_type_id = GetFacetAsType(context, query_self_const_id);
|
||||
auto op_id =
|
||||
MakeDestroyOpFunction(context, loc_id, self_type_id, parent_scope_id);
|
||||
auto op_id = MakeDestroyOpFunction(context, loc_id, self_type_id,
|
||||
parent_scope_id, format);
|
||||
return BuildCustomWitness(context, loc_id, query_self_const_id,
|
||||
query_specific_interface_id, {op_id});
|
||||
}
|
||||
@@ -406,7 +595,7 @@ static auto MakeDestroyWitness(
|
||||
static auto MakeIntFitsInWitness(
|
||||
Context& context, SemIR::LocId loc_id,
|
||||
SemIR::ConstantId query_self_const_id,
|
||||
SemIR::SpecificInterfaceId query_specific_interface_id)
|
||||
SemIR::SpecificInterfaceId query_specific_interface_id, bool build_witness)
|
||||
-> std::optional<SemIR::InstId> {
|
||||
auto query_specific_interface =
|
||||
context.specific_interfaces().Get(query_specific_interface_id);
|
||||
@@ -469,6 +658,10 @@ static auto MakeIntFitsInWitness(
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (!build_witness) {
|
||||
return SemIR::InstId::None;
|
||||
}
|
||||
|
||||
return BuildCustomWitness(context, loc_id, query_self_const_id,
|
||||
query_specific_interface_id, {});
|
||||
}
|
||||
@@ -476,17 +669,18 @@ static auto MakeIntFitsInWitness(
|
||||
auto LookupCustomWitness(Context& context, SemIR::LocId loc_id,
|
||||
CoreInterface core_interface,
|
||||
SemIR::ConstantId query_self_const_id,
|
||||
SemIR::SpecificInterfaceId query_specific_interface_id)
|
||||
-> std::optional<SemIR::InstId> {
|
||||
SemIR::SpecificInterfaceId query_specific_interface_id,
|
||||
bool build_witness) -> std::optional<SemIR::InstId> {
|
||||
switch (core_interface) {
|
||||
case CoreInterface::Destroy:
|
||||
return MakeDestroyWitness(context, loc_id, query_self_const_id,
|
||||
query_specific_interface_id);
|
||||
query_specific_interface_id, build_witness);
|
||||
case CoreInterface::IntFitsIn:
|
||||
return MakeIntFitsInWitness(context, loc_id, query_self_const_id,
|
||||
query_specific_interface_id);
|
||||
case CoreInterface::CppUnsafeDeref:
|
||||
query_specific_interface_id, build_witness);
|
||||
case CoreInterface::Copy:
|
||||
case CoreInterface::CppUnsafeDeref:
|
||||
case CoreInterface::Default:
|
||||
case CoreInterface::Unknown:
|
||||
// TODO: Handle more interfaces, particularly copy, move, and conversion.
|
||||
return std::nullopt;
|
||||
|
||||
@@ -19,13 +19,20 @@ auto BuildCustomWitness(Context& context, SemIR::LocId loc_id,
|
||||
SemIR::SpecificInterfaceId query_specific_interface_id,
|
||||
llvm::ArrayRef<SemIR::InstId> values) -> SemIR::InstId;
|
||||
|
||||
// Builds a witness that the given type is copyable via a primitive copy.
|
||||
auto BuildPrimitiveCopyWitness(
|
||||
Context& context, SemIR::LocId loc_id, SemIR::NameScopeId parent_scope_id,
|
||||
SemIR::ConstantId query_self_const_id,
|
||||
SemIR::SpecificInterfaceId query_specific_interface_id) -> SemIR::InstId;
|
||||
|
||||
// Significant interfaces in `Core` which correspond to language features and
|
||||
// can have custom witnesses.
|
||||
enum class CoreInterface {
|
||||
Copy,
|
||||
CppUnsafeDeref,
|
||||
Default,
|
||||
Destroy,
|
||||
IntFitsIn,
|
||||
CppUnsafeDeref,
|
||||
|
||||
Unknown,
|
||||
};
|
||||
@@ -38,11 +45,15 @@ auto GetCoreInterface(Context& context, SemIR::InterfaceId interface_id)
|
||||
// Returns a witness for a `CoreInterface` `CustomWitness`. A return value of
|
||||
// `None` indicates a non-final witness should be produced, while `std::nullopt`
|
||||
// indicates the query is final and no witness can be produced.
|
||||
//
|
||||
// If `build_witness` is false, this function always returns `None` as the
|
||||
// witness, whether it would be final or not. It is used to indicate the
|
||||
// presence of such a witness without adding instructions for it.
|
||||
auto LookupCustomWitness(Context& context, SemIR::LocId loc_id,
|
||||
CoreInterface core_interface,
|
||||
SemIR::ConstantId query_self_const_id,
|
||||
SemIR::SpecificInterfaceId query_specific_interface_id)
|
||||
-> std::optional<SemIR::InstId>;
|
||||
SemIR::SpecificInterfaceId query_specific_interface_id,
|
||||
bool build_witness) -> std::optional<SemIR::InstId>;
|
||||
|
||||
} // namespace Carbon::Check
|
||||
|
||||
|
||||
+413
-106
@@ -36,6 +36,7 @@
|
||||
#include "toolchain/sem_ir/impl.h"
|
||||
#include "toolchain/sem_ir/inst_categories.h"
|
||||
#include "toolchain/sem_ir/inst_kind.h"
|
||||
#include "toolchain/sem_ir/specific_named_constraint.h"
|
||||
#include "toolchain/sem_ir/typed_insts.h"
|
||||
|
||||
namespace Carbon::Check {
|
||||
@@ -439,14 +440,11 @@ static auto GetConstantValue(EvalContext& eval_context, SemIR::InstId inst_id,
|
||||
|
||||
// Issue a suitable diagnostic for an instruction that evaluated to a
|
||||
// non-constant value but was required to evaluate to a constant.
|
||||
static auto DiagnoseNonConstantValue(EvalContext& eval_context,
|
||||
SemIR::InstId inst_id) -> void {
|
||||
if (inst_id != SemIR::ErrorInst::InstId) {
|
||||
CARBON_DIAGNOSTIC(EvalRequiresConstantValue, Error,
|
||||
"expression is runtime; expected constant");
|
||||
eval_context.emitter().Emit(eval_context.GetDiagnosticLoc({inst_id}),
|
||||
EvalRequiresConstantValue);
|
||||
}
|
||||
static auto DiagnoseNonConstantValue(Context& context, SemIR::LocId loc_id)
|
||||
-> void {
|
||||
CARBON_DIAGNOSTIC(EvalRequiresConstantValue, Error,
|
||||
"expression is runtime; expected constant");
|
||||
context.emitter().Emit(loc_id, EvalRequiresConstantValue);
|
||||
}
|
||||
|
||||
// Gets a constant value for an `inst_id`, diagnosing when the input is not a
|
||||
@@ -457,6 +455,11 @@ static auto RequireConstantValue(EvalContext& eval_context,
|
||||
if (!inst_id.has_value()) {
|
||||
return SemIR::InstId::None;
|
||||
}
|
||||
if (inst_id == SemIR::ErrorInst::InstId) {
|
||||
*phase = Phase::UnknownDueToError;
|
||||
return SemIR::ErrorInst::InstId;
|
||||
}
|
||||
|
||||
auto const_id = eval_context.GetConstantValue(inst_id);
|
||||
*phase =
|
||||
LatestPhase(*phase, GetPhase(eval_context.constant_values(), const_id));
|
||||
@@ -464,7 +467,8 @@ static auto RequireConstantValue(EvalContext& eval_context,
|
||||
return eval_context.constant_values().GetInstId(const_id);
|
||||
}
|
||||
|
||||
DiagnoseNonConstantValue(eval_context, inst_id);
|
||||
DiagnoseNonConstantValue(eval_context.context(),
|
||||
eval_context.GetDiagnosticLoc({inst_id}));
|
||||
*phase = Phase::UnknownDueToError;
|
||||
return SemIR::ErrorInst::InstId;
|
||||
}
|
||||
@@ -541,7 +545,8 @@ static auto GetConstantValue(EvalContext& eval_context,
|
||||
}
|
||||
|
||||
// Otherwise, this is a normal instruction.
|
||||
if (OperandIsDependent(eval_context.context(), inst_id)) {
|
||||
if (OperandDependence(eval_context.context(), inst_id) ==
|
||||
SemIR::ConstantDependence::Template) {
|
||||
*phase = LatestPhase(*phase, Phase::TemplateSymbolic);
|
||||
}
|
||||
return inst_id;
|
||||
@@ -588,24 +593,29 @@ static auto GetConstantValue(EvalContext& eval_context,
|
||||
Phase* phase) -> SemIR::InstBlockId = delete;
|
||||
|
||||
// If the given instruction block contains only constants, returns a
|
||||
// corresponding block of those values.
|
||||
static auto GetConstantValue(EvalContext& eval_context,
|
||||
SemIR::InstBlockId inst_block_id, Phase* phase)
|
||||
-> SemIR::InstBlockId {
|
||||
// corresponding block of those values. Ignores the instructions in the
|
||||
// specified range of indexes, replacing those elements with `None`.
|
||||
static auto GetConstantBlockValueIgnoringIndexRange(
|
||||
EvalContext& eval_context, SemIR::InstBlockId inst_block_id, Phase* phase,
|
||||
std::pair<int, int> ignored_range) -> SemIR::InstBlockId {
|
||||
if (!inst_block_id.has_value()) {
|
||||
return SemIR::InstBlockId::None;
|
||||
}
|
||||
auto insts = eval_context.inst_blocks().Get(inst_block_id);
|
||||
llvm::SmallVector<SemIR::InstId> const_insts;
|
||||
for (auto inst_id : insts) {
|
||||
auto const_inst_id = GetConstantValue(eval_context, inst_id, phase);
|
||||
if (!const_inst_id.has_value()) {
|
||||
return SemIR::InstBlockId::None;
|
||||
auto const_inst_id = SemIR::InstId::None;
|
||||
if (static_cast<int>(const_insts.size()) < ignored_range.first ||
|
||||
static_cast<int>(const_insts.size()) >= ignored_range.second) {
|
||||
const_inst_id = GetConstantValue(eval_context, inst_id, phase);
|
||||
if (!const_inst_id.has_value()) {
|
||||
return SemIR::InstBlockId::None;
|
||||
}
|
||||
}
|
||||
|
||||
// Once we leave the small buffer, we know the first few elements are all
|
||||
// constant, so it's likely that the entire block is constant. Resize to the
|
||||
// target size given that we're going to allocate memory now anyway.
|
||||
// constant, so it's likely that the entire block is constant. Resize to
|
||||
// the target size given that we're going to allocate memory now anyway.
|
||||
if (const_insts.size() == const_insts.capacity()) {
|
||||
const_insts.reserve(insts.size());
|
||||
}
|
||||
@@ -617,6 +627,15 @@ static auto GetConstantValue(EvalContext& eval_context,
|
||||
return eval_context.inst_blocks().AddCanonical(const_insts);
|
||||
}
|
||||
|
||||
// If the given instruction block contains only constants, returns a
|
||||
// corresponding block of those values.
|
||||
static auto GetConstantValue(EvalContext& eval_context,
|
||||
SemIR::InstBlockId inst_block_id, Phase* phase)
|
||||
-> SemIR::InstBlockId {
|
||||
return GetConstantBlockValueIgnoringIndexRange(eval_context, inst_block_id,
|
||||
phase, {0, 0});
|
||||
}
|
||||
|
||||
// Compute the constant value of a type block. This may be different from the
|
||||
// input type block if we have known generic arguments.
|
||||
static auto GetConstantValue(EvalContext& eval_context,
|
||||
@@ -704,6 +723,7 @@ static auto GetConstantFacetTypeInfo(EvalContext& eval_context,
|
||||
|
||||
info.extend_constraints.reserve(orig.extend_constraints.size());
|
||||
for (const auto& extend : orig.extend_constraints) {
|
||||
// TODO: Add GetConstantValue for SpecificInterface.
|
||||
info.extend_constraints.push_back(
|
||||
{.interface_id = extend.interface_id,
|
||||
.specific_id =
|
||||
@@ -712,6 +732,7 @@ static auto GetConstantFacetTypeInfo(EvalContext& eval_context,
|
||||
|
||||
info.self_impls_constraints.reserve(orig.self_impls_constraints.size());
|
||||
for (const auto& self_impls : orig.self_impls_constraints) {
|
||||
// TODO: Add GetConstantValue for SpecificInterface.
|
||||
info.self_impls_constraints.push_back(
|
||||
{.interface_id = self_impls.interface_id,
|
||||
.specific_id =
|
||||
@@ -720,6 +741,7 @@ static auto GetConstantFacetTypeInfo(EvalContext& eval_context,
|
||||
|
||||
info.extend_named_constraints.reserve(orig.extend_named_constraints.size());
|
||||
for (const auto& extend : orig.extend_named_constraints) {
|
||||
// TODO: Add GetConstantValue for SpecificNamedConstraint.
|
||||
info.extend_named_constraints.push_back(
|
||||
{.named_constraint_id = extend.named_constraint_id,
|
||||
.specific_id =
|
||||
@@ -729,12 +751,41 @@ static auto GetConstantFacetTypeInfo(EvalContext& eval_context,
|
||||
info.self_impls_named_constraints.reserve(
|
||||
orig.self_impls_named_constraints.size());
|
||||
for (const auto& self_impls : orig.self_impls_named_constraints) {
|
||||
// TODO: Add GetConstantValue for SpecificNamedConstraint.
|
||||
info.self_impls_named_constraints.push_back(
|
||||
{.named_constraint_id = self_impls.named_constraint_id,
|
||||
.specific_id =
|
||||
GetConstantValue(eval_context, self_impls.specific_id, phase)});
|
||||
}
|
||||
|
||||
info.type_impls_interfaces.reserve(orig.type_impls_interfaces.size());
|
||||
for (const auto& type_impls : orig.type_impls_interfaces) {
|
||||
info.type_impls_interfaces.push_back(
|
||||
{.self_type =
|
||||
GetConstantValue(eval_context, type_impls.self_type, phase),
|
||||
// TODO: Add GetConstantValue for SpecificInterface.
|
||||
.specific_interface = {
|
||||
.interface_id = type_impls.specific_interface.interface_id,
|
||||
.specific_id = GetConstantValue(
|
||||
eval_context, type_impls.specific_interface.specific_id,
|
||||
phase)}});
|
||||
}
|
||||
|
||||
info.type_impls_named_constraints.reserve(
|
||||
orig.type_impls_named_constraints.size());
|
||||
for (const auto& type_impls : orig.type_impls_named_constraints) {
|
||||
info.type_impls_named_constraints.push_back(
|
||||
{.self_type =
|
||||
GetConstantValue(eval_context, type_impls.self_type, phase),
|
||||
// TODO: Add GetConstantValue for SpecificNamedConstraint.
|
||||
.specific_named_constraint = {
|
||||
.named_constraint_id =
|
||||
type_impls.specific_named_constraint.named_constraint_id,
|
||||
.specific_id = GetConstantValue(
|
||||
eval_context, type_impls.specific_named_constraint.specific_id,
|
||||
phase)}});
|
||||
}
|
||||
|
||||
// Rewrite constraints are resolved first before replacing them with their
|
||||
// canonical instruction, so that in a `WhereExpr` we can work with the
|
||||
// `ImplWitnessAccess` references to `.Self` on the LHS of the constraints
|
||||
@@ -958,6 +1009,14 @@ static auto ResolveSpecificDeclForInst(EvalContext& eval_context,
|
||||
ResolveSpecificDeclForSpecificId(eval_context,
|
||||
constraint.specific_id);
|
||||
}
|
||||
for (const auto& type_impls : info.type_impls_interfaces) {
|
||||
ResolveSpecificDeclForSpecificId(
|
||||
eval_context, type_impls.specific_interface.specific_id);
|
||||
}
|
||||
for (const auto& type_impls : info.type_impls_named_constraints) {
|
||||
ResolveSpecificDeclForSpecificId(
|
||||
eval_context, type_impls.specific_named_constraint.specific_id);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CARBON_KIND(SemIR::SpecificId specific_id): {
|
||||
@@ -2088,6 +2147,39 @@ static auto TryEvalCall(EvalContext& outer_eval_context, SemIR::LocId loc_id,
|
||||
SemIR::SpecificId specific_id,
|
||||
SemIR::InstBlockId args_id) -> SemIR::ConstantId;
|
||||
|
||||
// Returns the range of parameter indexes that contain the return storage for
|
||||
// this function call.
|
||||
static auto GetReturnStorageParamIndexRange(EvalContext& eval_context,
|
||||
const SemIR::Callee& callee)
|
||||
-> std::pair<int, int> {
|
||||
if (const auto* callee_function =
|
||||
std::get_if<SemIR::CalleeFunction>(&callee)) {
|
||||
const auto& function =
|
||||
eval_context.functions().Get(callee_function->function_id);
|
||||
return {function.call_param_ranges.return_begin().index,
|
||||
function.call_param_ranges.return_end().index};
|
||||
}
|
||||
|
||||
return {0, 0};
|
||||
}
|
||||
|
||||
// Replace the `args_id` field of a call with its constant value. The return
|
||||
// storage argument, if any, is instead replaced with `None`.
|
||||
static auto ReplaceCallArgsFieldWithConstantValue(EvalContext& eval_context,
|
||||
const SemIR::Callee& callee,
|
||||
SemIR::Call* call,
|
||||
Phase* phase) -> bool {
|
||||
auto return_storage_param_index_range =
|
||||
GetReturnStorageParamIndexRange(eval_context, callee);
|
||||
auto args_id = GetConstantBlockValueIgnoringIndexRange(
|
||||
eval_context, call->args_id, phase, return_storage_param_index_range);
|
||||
if (!args_id.has_value() && call->args_id.has_value()) {
|
||||
return false;
|
||||
}
|
||||
call->args_id = args_id;
|
||||
return IsConstantOrError(*phase);
|
||||
}
|
||||
|
||||
// Makes a constant for a call instruction.
|
||||
static auto MakeConstantForCall(EvalContext& eval_context,
|
||||
SemIR::InstId inst_id, SemIR::Call call)
|
||||
@@ -2102,6 +2194,10 @@ static auto MakeConstantForCall(EvalContext& eval_context,
|
||||
return SemIR::ErrorInst::ConstantId;
|
||||
}
|
||||
|
||||
// If the callee is a C++ thunk, modify the `call` to directly call
|
||||
// the thunk's callee.
|
||||
MaybeModifyCppThunkCallForConstEval(eval_context.context(), &call);
|
||||
|
||||
// Find the constant value of the callee.
|
||||
bool has_constant_callee = ReplaceFieldWithConstantValue(
|
||||
eval_context, &call, &SemIR::Call::callee_id, &phase);
|
||||
@@ -2129,8 +2225,8 @@ static auto MakeConstantForCall(EvalContext& eval_context,
|
||||
bool has_constant_operands =
|
||||
has_constant_callee &&
|
||||
ReplaceTypeWithConstantValue(eval_context, inst_id, &call, &phase) &&
|
||||
ReplaceFieldWithConstantValue(eval_context, &call, &SemIR::Call::args_id,
|
||||
&phase);
|
||||
ReplaceCallArgsFieldWithConstantValue(eval_context, callee, &call,
|
||||
&phase);
|
||||
if (phase == Phase::UnknownDueToError) {
|
||||
return SemIR::ErrorInst::ConstantId;
|
||||
}
|
||||
@@ -2200,14 +2296,20 @@ static auto ComputeInstPhase(Context& context, SemIR::Inst inst) -> Phase {
|
||||
// TryEvalTypedInst to avoid repeated instantiation of common code.
|
||||
static auto ConvertEvalResultToConstantId(Context& context,
|
||||
ConstantEvalResult result,
|
||||
SemIR::InstKind orig_inst_kind,
|
||||
Phase orig_phase)
|
||||
-> SemIR::ConstantId {
|
||||
if (result.is_new()) {
|
||||
return MakeConstantResult(
|
||||
context, result.new_inst(),
|
||||
result.same_phase_as_inst()
|
||||
? orig_phase
|
||||
: ComputeInstPhase(context, result.new_inst()));
|
||||
auto is_symbolic_only =
|
||||
orig_inst_kind.constant_kind() == SemIR::InstConstantKind::SymbolicOnly;
|
||||
auto new_phase = result.same_phase_as_inst()
|
||||
? orig_phase
|
||||
: ComputeInstPhase(context, result.new_inst());
|
||||
CARBON_CHECK(!is_symbolic_only || new_phase > Phase::Concrete ||
|
||||
result.new_inst().kind() != orig_inst_kind,
|
||||
"SymbolicOnly instruction `{0}` has a concrete value",
|
||||
orig_inst_kind);
|
||||
return MakeConstantResult(context, result.new_inst(), new_phase);
|
||||
}
|
||||
return result.existing();
|
||||
}
|
||||
@@ -2288,11 +2390,12 @@ static auto TryEvalTypedInst(EvalContext& eval_context, SemIR::InstId inst_id,
|
||||
return ConvertEvalResultToConstantId(
|
||||
eval_context.context(),
|
||||
EvalConstantInst(eval_context.context(), inst_id, inst.As<InstT>()),
|
||||
phase);
|
||||
InstT::Kind, phase);
|
||||
} else {
|
||||
return ConvertEvalResultToConstantId(
|
||||
eval_context.context(),
|
||||
EvalConstantInst(eval_context.context(), inst.As<InstT>()), phase);
|
||||
EvalConstantInst(eval_context.context(), inst.As<InstT>()),
|
||||
InstT::Kind, phase);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2389,6 +2492,7 @@ auto TryEvalTypedInst<SemIR::SymbolicBindingType>(EvalContext& eval_context,
|
||||
return ConvertEvalResultToConstantId(
|
||||
eval_context.context(),
|
||||
EvalConstantInst(eval_context.context(), access),
|
||||
SemIR::SymbolicBindingType::Kind,
|
||||
ComputeInstPhase(eval_context.context(), access));
|
||||
}
|
||||
}
|
||||
@@ -2419,6 +2523,24 @@ auto TryEvalTypedInst<SemIR::SymbolicBindingType>(EvalContext& eval_context,
|
||||
return MakeConstantResult(eval_context.context(), inst, phase);
|
||||
}
|
||||
|
||||
template <>
|
||||
auto TryEvalTypedInst<SemIR::Temporary>(EvalContext& eval_context,
|
||||
SemIR::InstId inst_id, SemIR::Inst inst)
|
||||
-> SemIR::ConstantId {
|
||||
auto temporary = inst.As<SemIR::Temporary>();
|
||||
temporary.storage_id = SemIR::InstId::None;
|
||||
|
||||
Phase phase = Phase::Concrete;
|
||||
if (!ReplaceTypeWithConstantValue(eval_context, inst_id, &temporary,
|
||||
&phase) ||
|
||||
!ReplaceFieldWithConstantValue(eval_context, &temporary,
|
||||
&SemIR::Temporary::init_id, &phase)) {
|
||||
return SemIR::ConstantId::NotConstant;
|
||||
}
|
||||
|
||||
return MakeConstantResult(eval_context.context(), temporary, phase);
|
||||
}
|
||||
|
||||
// Returns whether `const_id` is the same constant facet value as
|
||||
// `facet_value_inst_id`.
|
||||
//
|
||||
@@ -2430,93 +2552,250 @@ static auto IsSameFacetValue(Context& context, SemIR::ConstantId const_id,
|
||||
return canon_const_id == context.constant_values().Get(facet_value_inst_id);
|
||||
}
|
||||
|
||||
static auto AddRequirementBase(Context& context,
|
||||
SemIR::RequirementBaseFacetType base,
|
||||
SemIR::FacetTypeInfo* info, Phase* phase)
|
||||
-> void {
|
||||
auto base_type_inst_id =
|
||||
context.constant_values().GetConstantTypeInstId(base.base_type_inst_id);
|
||||
if (base_type_inst_id == SemIR::ErrorInst::TypeInstId) {
|
||||
*phase = Phase::UnknownDueToError;
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto base_facet_type =
|
||||
context.insts().TryGetAs<SemIR::FacetType>(base_type_inst_id)) {
|
||||
const auto& base_info =
|
||||
context.facet_types().Get(base_facet_type->facet_type_id);
|
||||
info->extend_constraints.append(base_info.extend_constraints);
|
||||
info->self_impls_constraints.append(base_info.self_impls_constraints);
|
||||
info->type_impls_interfaces.append(base_info.type_impls_interfaces);
|
||||
info->type_impls_named_constraints.append(
|
||||
base_info.type_impls_named_constraints);
|
||||
info->rewrite_constraints.append(base_info.rewrite_constraints);
|
||||
info->other_requirements |= base_info.other_requirements;
|
||||
}
|
||||
}
|
||||
|
||||
static auto AddRequirementRewrite(Context& context,
|
||||
SemIR::RequirementRewrite rewrite,
|
||||
SemIR::FacetTypeInfo* info, Phase* phase)
|
||||
-> void {
|
||||
auto lhs_id = context.constant_values().GetConstantInstId(rewrite.lhs_id);
|
||||
auto rhs_id = context.constant_values().GetConstantInstId(rewrite.rhs_id);
|
||||
if (lhs_id == SemIR::ErrorInst::InstId ||
|
||||
rhs_id == SemIR::ErrorInst::InstId) {
|
||||
*phase = Phase::UnknownDueToError;
|
||||
return;
|
||||
}
|
||||
if (!rhs_id.has_value()) {
|
||||
// The RHS may be an arbitrary expression, which means it could have a
|
||||
// runtime value, which we reject since we can't evaluate that.
|
||||
DiagnoseNonConstantValue(context, SemIR::LocId(rewrite.rhs_id));
|
||||
*phase = Phase::UnknownDueToError;
|
||||
return;
|
||||
}
|
||||
|
||||
// The FacetTypeInfo must hold canonical IDs for constant comparison, yet here
|
||||
// we must insert the non-canonical IDs:
|
||||
// * Rewrite constraints are resolved once the FacetTypeInfo is fully
|
||||
// constructed in order to produce the constant value of the facet type.
|
||||
// That resolution step needs the non-canonical insts to do its job
|
||||
// correctly. For instance, the LHS may be a `ImplWitnessAccessSubstituted`
|
||||
// instruction which preserves which element in the witness is being
|
||||
// assigned to but evaluates to the RHS of some other rewrite. So the
|
||||
// constant value would be incorrect to use.
|
||||
// * We use the id of the non-canonical RHS instruction as a hint to order
|
||||
// diagnostics in the resolution of rewrites, so that they can usually refer
|
||||
// to the rewrites in the same order as they are written in the code. Using
|
||||
// the constant value of the RHS reorders the diagnostics in a worse way.
|
||||
// * The final step of constructing the facet type from the WhereExpr
|
||||
// canonicalizes all the instructions, so we don't need to store canonical
|
||||
// values here. We only need to use canonical values if we need to observe
|
||||
// the constant value, such as to determine in the RHS has a runtime value
|
||||
// above.
|
||||
info->rewrite_constraints.push_back(
|
||||
{.lhs_id = rewrite.lhs_id, .rhs_id = rewrite.rhs_id});
|
||||
}
|
||||
|
||||
static auto AddRequirementImpls(Context& context, SemIR::RequirementImpls impls,
|
||||
SemIR::InstId period_self_id,
|
||||
SemIR::FacetTypeInfo* info, Phase* phase)
|
||||
-> void {
|
||||
auto lhs_id = context.constant_values().GetConstantInstId(impls.lhs_id);
|
||||
auto rhs_id = context.constant_values().GetConstantInstId(impls.rhs_id);
|
||||
if (lhs_id == SemIR::ErrorInst::InstId ||
|
||||
rhs_id == SemIR::ErrorInst::InstId) {
|
||||
*phase = Phase::UnknownDueToError;
|
||||
return;
|
||||
}
|
||||
|
||||
if (rhs_id == SemIR::TypeType::TypeInstId) {
|
||||
// `<type> impls type` -> nothing to do.
|
||||
return;
|
||||
}
|
||||
|
||||
auto facet_type = context.insts().GetAs<SemIR::FacetType>(rhs_id);
|
||||
const auto& rhs = context.facet_types().Get(facet_type.facet_type_id);
|
||||
|
||||
if (IsSameFacetValue(context, context.constant_values().Get(lhs_id),
|
||||
period_self_id)) {
|
||||
// A facet type with `.Self impls <RHS facet type>`. Whatever the RHS facet
|
||||
// type constrains for `.Self` gets forwarded to the output facet type to
|
||||
// also constrain `.Self`. Nothing on the RHS of `impls` can extend the
|
||||
// resulting facet type.
|
||||
llvm::append_range(info->self_impls_constraints, rhs.extend_constraints);
|
||||
llvm::append_range(info->self_impls_constraints,
|
||||
rhs.self_impls_constraints);
|
||||
llvm::append_range(info->self_impls_named_constraints,
|
||||
rhs.extend_named_constraints);
|
||||
llvm::append_range(info->self_impls_named_constraints,
|
||||
rhs.self_impls_named_constraints);
|
||||
llvm::append_range(info->type_impls_interfaces, rhs.type_impls_interfaces);
|
||||
llvm::append_range(info->type_impls_named_constraints,
|
||||
rhs.type_impls_named_constraints);
|
||||
} else {
|
||||
// Consider `I where C(.Self) impls (J(.Self) where .Self impls K(.Self))`,
|
||||
// when we are evaluating the `C(.Self) impls (<facet type>)` requirement.
|
||||
// The <facet type> is our `rhs` here, and it will contain:
|
||||
// * extend constraint: J(.Self)
|
||||
// * self impls constraint: K(.Self)
|
||||
//
|
||||
// The value of `.Self` changes where we cross a `where` operator. This
|
||||
// means extend constraints retain their original `.Self`, but self impls
|
||||
// constraints should have their `.Self` replaced by the LHS of the impls
|
||||
// requirement.
|
||||
//
|
||||
// However that is not quite enough. The view of the LHS of the impls
|
||||
// requirement should be a facet with a facet type of the RHS extend
|
||||
// constraints. In this case the LHS is C(.Self) and the RHS facet type is
|
||||
// `J(.Self) where .Self impls K(.Self)`. The RHS facet type has impls
|
||||
// constraints (which are on the RHS of a `where` operator), in which their
|
||||
// `.Self` should be replaced by `C(.Self)` converted to the RHS facet
|
||||
// type's extend constraints (which are on the LHS of a `where` operator),
|
||||
// which is `C(.Self) as J(.Self)`. It should be enough to convert the LHS
|
||||
// type to the type of the `.Self` that it is replacing, as that contains
|
||||
// the extend constraints.
|
||||
//
|
||||
// So the final RHS facet type to be merged into `info` is:
|
||||
//
|
||||
// `J(.Self) where (C(.Self) as J(.Self)) impls K(C(.Self) as J(.Self))`.
|
||||
|
||||
auto lhs_facet_or_type = GetCanonicalFacetOrTypeValue(context, lhs_id);
|
||||
|
||||
auto impls_interface = [&](SemIR::SpecificInterface si)
|
||||
-> SemIR::FacetTypeInfo::TypeImplsInterface {
|
||||
return {lhs_facet_or_type, si};
|
||||
};
|
||||
auto impls_constraint = [&](SemIR::SpecificNamedConstraint sc)
|
||||
-> SemIR::FacetTypeInfo::TypeImplsNamedConstraint {
|
||||
return {lhs_facet_or_type, sc};
|
||||
};
|
||||
|
||||
// Extend constraints are copied over without replacing anything, but are
|
||||
// converted to type impls constraints so they apply to the LHS type.
|
||||
llvm::append_range(
|
||||
info->type_impls_interfaces,
|
||||
llvm::map_range(rhs.extend_constraints, impls_interface));
|
||||
llvm::append_range(
|
||||
info->type_impls_named_constraints,
|
||||
llvm::map_range(rhs.extend_named_constraints, impls_constraint));
|
||||
|
||||
// To replace the `.Self` in `.Self impls X` we convert from a self impls
|
||||
// constraint to a type impls constraint where the type is the impls LHS
|
||||
// type. We must also replace any `.Self` references in the constraint in
|
||||
// the same way. The LHS type needs to be converted to a facet with its type
|
||||
// containing the RHS facet type's extend constraints so that the extend
|
||||
// constraints can be referenced in impls constraints.
|
||||
//
|
||||
// TODO: Convert the LHS used in the TypeImplsNamedConstraint to a facet
|
||||
// with the RHS extend constraints (interfaces and named constraints).
|
||||
//
|
||||
// TODO: Replace `.Self` with the LHS type as a facet with the RHS extend
|
||||
// constraints.
|
||||
llvm::append_range(
|
||||
info->type_impls_interfaces,
|
||||
llvm::map_range(rhs.self_impls_constraints, impls_interface));
|
||||
llvm::append_range(
|
||||
info->type_impls_named_constraints,
|
||||
llvm::map_range(rhs.self_impls_named_constraints, impls_constraint));
|
||||
|
||||
// Type impls constraints are copied in, but need to have their `.Self`
|
||||
// references replaced by the impls LHS type. Like above, the LHS type
|
||||
// should be converted to a facet type containing the RHS facet type's
|
||||
// extend constraints.
|
||||
//
|
||||
// TODO: Convert the LHS used in the TypeImplsNamedConstraint to a facet
|
||||
// with the RHS extend constraints (interfaces and named constraints).
|
||||
//
|
||||
// TODO: Replace `.Self` with the LHS type as a facet with the RHS extend
|
||||
// constraints.
|
||||
llvm::append_range(info->type_impls_interfaces, rhs.type_impls_interfaces);
|
||||
llvm::append_range(info->type_impls_named_constraints,
|
||||
rhs.type_impls_named_constraints);
|
||||
}
|
||||
|
||||
// Other requirements are copied in.
|
||||
llvm::append_range(info->rewrite_constraints, rhs.rewrite_constraints);
|
||||
info->other_requirements |= rhs.other_requirements;
|
||||
}
|
||||
|
||||
// Add the constraints from the WhereExpr instruction into a FacetTypeInfo in
|
||||
// order to construct a FacetType constant value.
|
||||
//
|
||||
// TODO: Convert this to an EvalConstantInst function. This will require
|
||||
// providing a `GetConstantValue` overload for a requirement block.
|
||||
template <>
|
||||
auto TryEvalTypedInst<SemIR::WhereExpr>(EvalContext& eval_context,
|
||||
SemIR::InstId inst_id, SemIR::Inst inst)
|
||||
-> SemIR::ConstantId {
|
||||
SemIR::InstId where_inst_id,
|
||||
SemIR::Inst inst) -> SemIR::ConstantId {
|
||||
auto typed_inst = inst.As<SemIR::WhereExpr>();
|
||||
|
||||
Phase phase = Phase::Concrete;
|
||||
SemIR::FacetTypeInfo info;
|
||||
|
||||
// Add the constraints from the `WhereExpr` instruction into `info`.
|
||||
if (typed_inst.requirements_id.has_value()) {
|
||||
auto insts = eval_context.inst_blocks().Get(typed_inst.requirements_id);
|
||||
for (auto inst_id : insts) {
|
||||
if (auto base =
|
||||
eval_context.insts().TryGetAs<SemIR::RequirementBaseFacetType>(
|
||||
inst_id)) {
|
||||
if (base->base_type_inst_id == SemIR::ErrorInst::TypeInstId) {
|
||||
return SemIR::ErrorInst::ConstantId;
|
||||
}
|
||||
if (typed_inst.period_self_id == SemIR::ErrorInst::InstId) {
|
||||
return SemIR::ErrorInst::ConstantId;
|
||||
}
|
||||
|
||||
if (auto base_facet_type =
|
||||
eval_context.insts().TryGetAs<SemIR::FacetType>(
|
||||
base->base_type_inst_id)) {
|
||||
const auto& base_info =
|
||||
eval_context.facet_types().Get(base_facet_type->facet_type_id);
|
||||
info.extend_constraints.append(base_info.extend_constraints);
|
||||
info.self_impls_constraints.append(base_info.self_impls_constraints);
|
||||
info.rewrite_constraints.append(base_info.rewrite_constraints);
|
||||
info.other_requirements |= base_info.other_requirements;
|
||||
}
|
||||
} else if (auto rewrite =
|
||||
eval_context.insts().TryGetAs<SemIR::RequirementRewrite>(
|
||||
inst_id)) {
|
||||
info.rewrite_constraints.push_back(
|
||||
{.lhs_id = rewrite->lhs_id, .rhs_id = rewrite->rhs_id});
|
||||
} else if (auto impls =
|
||||
eval_context.insts().TryGetAs<SemIR::RequirementImpls>(
|
||||
inst_id)) {
|
||||
SemIR::ConstantId lhs_const_id =
|
||||
eval_context.GetConstantValue(impls->lhs_id);
|
||||
SemIR::ConstantId rhs_const_id =
|
||||
eval_context.GetConstantValue(impls->rhs_id);
|
||||
if (IsSameFacetValue(eval_context.context(), lhs_const_id,
|
||||
typed_inst.period_self_id)) {
|
||||
auto rhs_inst_id =
|
||||
eval_context.constant_values().GetInstId(rhs_const_id);
|
||||
if (rhs_inst_id == SemIR::ErrorInst::InstId) {
|
||||
// `.Self impls <error>`.
|
||||
return SemIR::ErrorInst::ConstantId;
|
||||
} else if (rhs_inst_id == SemIR::TypeType::TypeInstId) {
|
||||
// `.Self impls type` -> nothing to do.
|
||||
} else {
|
||||
auto facet_type = eval_context.insts().GetAs<SemIR::FacetType>(
|
||||
RequireConstantValue(eval_context, impls->rhs_id, &phase));
|
||||
const auto& more_info =
|
||||
eval_context.facet_types().Get(facet_type.facet_type_id);
|
||||
// The way to prevent lookup into the interface requirements of a
|
||||
// facet type is to put it to the right of a `.Self impls`, which we
|
||||
// accomplish by putting them into `self_impls_constraints`.
|
||||
llvm::append_range(info.self_impls_constraints,
|
||||
more_info.extend_constraints);
|
||||
llvm::append_range(info.self_impls_constraints,
|
||||
more_info.self_impls_constraints);
|
||||
llvm::append_range(info.self_impls_named_constraints,
|
||||
more_info.extend_named_constraints);
|
||||
llvm::append_range(info.self_impls_named_constraints,
|
||||
more_info.self_impls_named_constraints);
|
||||
// Other requirements are copied in.
|
||||
llvm::append_range(info.rewrite_constraints,
|
||||
more_info.rewrite_constraints);
|
||||
info.other_requirements |= more_info.other_requirements;
|
||||
}
|
||||
} else {
|
||||
// TODO: Handle `impls` constraints beyond `.Self impls`.
|
||||
info.other_requirements = true;
|
||||
}
|
||||
} else {
|
||||
// TODO: Handle other requirements.
|
||||
info.other_requirements = true;
|
||||
// Note that these requirement instructions don't have a constant value. That
|
||||
// means we have to look for errors inside them, we can't just look to see if
|
||||
// their constant value is an error.
|
||||
for (auto inst_id :
|
||||
eval_context.inst_blocks().GetOrEmpty(typed_inst.requirements_id)) {
|
||||
if (phase == Phase::UnknownDueToError) {
|
||||
// Abandon ship to save work once we've encountered an error.
|
||||
return SemIR::ErrorInst::ConstantId;
|
||||
}
|
||||
|
||||
auto inst = eval_context.insts().Get(inst_id);
|
||||
CARBON_KIND_SWITCH(inst) {
|
||||
case CARBON_KIND(SemIR::RequirementBaseFacetType base): {
|
||||
AddRequirementBase(eval_context.context(), base, &info, &phase);
|
||||
break;
|
||||
}
|
||||
case CARBON_KIND(SemIR::RequirementRewrite rewrite): {
|
||||
AddRequirementRewrite(eval_context.context(), rewrite, &info, &phase);
|
||||
break;
|
||||
}
|
||||
case CARBON_KIND(SemIR::RequirementImpls impls): {
|
||||
AddRequirementImpls(eval_context.context(), impls,
|
||||
typed_inst.period_self_id, &info, &phase);
|
||||
break;
|
||||
}
|
||||
case CARBON_KIND(SemIR::RequirementEquivalent _): {
|
||||
// TODO: Handle equality requirements.
|
||||
info.other_requirements = true;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
CARBON_FATAL("unexpected inst {0} in WhereExpr requirements block",
|
||||
inst);
|
||||
}
|
||||
}
|
||||
|
||||
auto const_info = GetConstantFacetTypeInfo(
|
||||
eval_context, SemIR::LocId(inst_id), info, &phase);
|
||||
eval_context, SemIR::LocId(where_inst_id), info, &phase);
|
||||
return MakeFacetTypeResult(eval_context.context(), const_info, phase);
|
||||
}
|
||||
|
||||
@@ -2544,7 +2823,7 @@ auto TryEvalInstUnsafe(Context& context, SemIR::InstId inst_id,
|
||||
auto TryEvalBlockForSpecific(Context& context, SemIR::LocId loc_id,
|
||||
SemIR::SpecificId specific_id,
|
||||
SemIR::GenericInstIndex::Region region)
|
||||
-> SemIR::InstBlockId {
|
||||
-> std::pair<SemIR::InstBlockId, bool> {
|
||||
auto generic_id = context.specifics().Get(specific_id).generic_id;
|
||||
auto eval_block_id = context.generics().Get(generic_id).GetEvalBlock(region);
|
||||
auto eval_block = context.inst_blocks().Get(eval_block_id);
|
||||
@@ -2566,15 +2845,19 @@ auto TryEvalBlockForSpecific(Context& context, SemIR::LocId loc_id,
|
||||
builder.Context(loc_id, ResolvingSpecificHere, specific_id);
|
||||
});
|
||||
|
||||
bool has_error = false;
|
||||
for (auto [i, inst_id] : llvm::enumerate(eval_block)) {
|
||||
auto const_id = TryEvalInstInContext(eval_context, inst_id,
|
||||
context.insts().Get(inst_id));
|
||||
if (const_id == SemIR::ErrorInst::ConstantId) {
|
||||
has_error = true;
|
||||
}
|
||||
result[i] = context.constant_values().GetInstId(const_id);
|
||||
CARBON_CHECK(result[i].has_value(), "Failed to evaluate {0} in eval block",
|
||||
context.insts().Get(inst_id));
|
||||
}
|
||||
|
||||
return context.inst_blocks().Add(result);
|
||||
return {context.inst_blocks().Add(result), has_error};
|
||||
}
|
||||
|
||||
// Information about the function call we are currently executing. Unlike
|
||||
@@ -2657,13 +2940,14 @@ class FunctionExecContext : public EvalContext {
|
||||
static auto HandleExecResult(FunctionExecContext& eval_context,
|
||||
SemIR::InstId inst_id, SemIR::ConstantId const_id)
|
||||
-> SemIR::ConstantId {
|
||||
if (!const_id.has_value() || !const_id.is_constant()) {
|
||||
DiagnoseNonConstantValue(eval_context, inst_id);
|
||||
return SemIR::ErrorInst::ConstantId;
|
||||
}
|
||||
if (const_id == SemIR::ErrorInst::ConstantId) {
|
||||
return const_id;
|
||||
}
|
||||
if (!const_id.has_value() || !const_id.is_constant()) {
|
||||
DiagnoseNonConstantValue(eval_context.context(),
|
||||
eval_context.GetDiagnosticLoc(inst_id));
|
||||
return SemIR::ErrorInst::ConstantId;
|
||||
}
|
||||
eval_context.locals().Update(inst_id, const_id);
|
||||
return SemIR::ConstantId::None;
|
||||
}
|
||||
@@ -2829,6 +3113,24 @@ auto TryExecTypedInst<SemIR::OutParam>(FunctionExecContext& eval_context,
|
||||
return SemIR::ConstantId::None;
|
||||
}
|
||||
|
||||
if (!eval_context.args()[param.index.index].has_value()) {
|
||||
// The argument will be `None` for an index corresponding to a return
|
||||
// storage argument for return values that have an in-place initializing
|
||||
// representation. Produce an opaque "out parameter" variable for now, so
|
||||
// that references to it can still successfully evaluate.
|
||||
//
|
||||
// TODO: Create and track mutable storage for the return value here. This is
|
||||
// necessary to support things like `returned var`.
|
||||
eval_context.locals().Insert(
|
||||
inst_id,
|
||||
MakeConstantResult(
|
||||
eval_context.context(),
|
||||
SemIR::VarStorage{.type_id = inst.type_id(),
|
||||
.pattern_id = SemIR::AbsoluteInstId::None},
|
||||
Phase::Concrete));
|
||||
return SemIR::ConstantId::None;
|
||||
}
|
||||
|
||||
return TryExecTypedParam(eval_context, inst_id, inst);
|
||||
}
|
||||
|
||||
@@ -2886,6 +3188,11 @@ static auto TryEvalCall(EvalContext& outer_eval_context, SemIR::LocId loc_id,
|
||||
return SemIR::ConstantId::NotConstant;
|
||||
}
|
||||
|
||||
if (specific_id.has_value()) {
|
||||
ResolveSpecificDefinition(outer_eval_context.context(), loc_id,
|
||||
specific_id);
|
||||
}
|
||||
|
||||
// TODO: Consider tracking the lowest and highest inst_id in the function and
|
||||
// using an array instead of a map. We would still need a map for instantiated
|
||||
// portions of a function template.
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#define CARBON_TOOLCHAIN_CHECK_EVAL_H_
|
||||
|
||||
#include "toolchain/check/context.h"
|
||||
#include "toolchain/sem_ir/generic.h"
|
||||
#include "toolchain/sem_ir/ids.h"
|
||||
#include "toolchain/sem_ir/inst.h"
|
||||
#include "toolchain/sem_ir/inst_kind.h"
|
||||
@@ -51,13 +52,11 @@ auto TryEvalInst(Context& context, InstT inst) -> SemIR::ConstantId {
|
||||
|
||||
// Evaluates the eval block for a region of a specific. Produces a block
|
||||
// containing the evaluated constant values of the instructions in the eval
|
||||
// block.
|
||||
//
|
||||
// TODO: Return whether any of the instructions produced contain an ErrorInst.
|
||||
// block. The returned bool indicates whether the region has an error.
|
||||
auto TryEvalBlockForSpecific(Context& context, SemIR::LocId loc_id,
|
||||
SemIR::SpecificId specific_id,
|
||||
SemIR::GenericInstIndex::Region region)
|
||||
-> SemIR::InstBlockId;
|
||||
-> std::pair<SemIR::InstBlockId, bool>;
|
||||
|
||||
} // namespace Carbon::Check
|
||||
|
||||
|
||||
@@ -127,17 +127,20 @@ auto EvalConstantInst(Context& /*context*/, SemIR::ValueBinding /*inst*/)
|
||||
|
||||
auto EvalConstantInst(Context& context, SemIR::InstId inst_id,
|
||||
SemIR::AcquireValue inst) -> ConstantEvalResult {
|
||||
SemIR::ConstantId const_id = SemIR::ConstantId::NotConstant;
|
||||
if (const auto* var_decl = GetAsClangVarDecl(context, inst.value_id)) {
|
||||
auto const_id =
|
||||
const_id =
|
||||
EvalCppVarDecl(context, SemIR::LocId(inst_id), var_decl, inst.type_id);
|
||||
if (const_id.has_value() && const_id.is_constant()) {
|
||||
return ConstantEvalResult::Existing(const_id);
|
||||
}
|
||||
|
||||
return ConstantEvalResult::NotConstant;
|
||||
} else if (auto temporary =
|
||||
context.insts().TryGetAs<SemIR::Temporary>(inst.value_id)) {
|
||||
const_id = context.constant_values().Get(temporary->init_id);
|
||||
}
|
||||
|
||||
return ConstantEvalResult::TODO;
|
||||
if (const_id.has_value() && const_id.is_constant()) {
|
||||
return ConstantEvalResult::Existing(const_id);
|
||||
}
|
||||
|
||||
return ConstantEvalResult::NotConstant;
|
||||
}
|
||||
|
||||
auto EvalConstantInst(Context& context, SemIR::ClassElementAccess inst)
|
||||
@@ -281,48 +284,35 @@ auto EvalConstantInst(Context& /*context*/, SemIR::FunctionDecl inst)
|
||||
|
||||
auto EvalConstantInst(Context& context, SemIR::InstId inst_id,
|
||||
SemIR::LookupImplWitness inst) -> ConstantEvalResult {
|
||||
// If the monomorphized query self is a FacetValue, we may get a witness from
|
||||
// it under limited circumstances. If no final witness is found though, we
|
||||
// don't need to preserve it for future evaluations, so we strip it from the
|
||||
// LookupImplWitness instruction to reduce the number of distinct constant
|
||||
// values.
|
||||
auto self_facet_value_inst_id = SemIR::InstId::None;
|
||||
if (auto facet_value = context.insts().TryGetAs<SemIR::FacetValue>(
|
||||
inst.query_self_inst_id)) {
|
||||
self_facet_value_inst_id =
|
||||
std::exchange(inst.query_self_inst_id, facet_value->type_inst_id);
|
||||
}
|
||||
|
||||
// The self value is canonicalized in order to produce a canonical
|
||||
// LookupImplWitness instruction, avoiding multiple constant values for
|
||||
// `<facet value>` and `<facet value>` as type, which always have the same
|
||||
// lookup result.
|
||||
auto self_facet_value_inst_id =
|
||||
inst.query_self_inst_id =
|
||||
GetCanonicalFacetOrTypeValue(context, inst.query_self_inst_id);
|
||||
|
||||
// When we look for a witness in the (facet) type of self, we may get a
|
||||
// concrete witness from a `FacetValue` (which is `self_facet_value_inst_id`)
|
||||
// in which case this instruction evaluates to that witness.
|
||||
//
|
||||
// If we only get a symbolic witness result though, then this instruction
|
||||
// evaluates to a `LookupImplWitness`. Since there was no concrete result in
|
||||
// the `FacetValue`, we don't need to preserve it. By looking through the
|
||||
// `FacetValue` at the type value it wraps to generate a more canonical value
|
||||
// for a symbolic `LookupImplWitness`. This makes us produce the same constant
|
||||
// value for symbolic lookups in `FacetValue(T)` and `T`, since they will
|
||||
// always have the same lookup result later, when `T` is replaced in a
|
||||
// specific by something that can provide a concrete witness.
|
||||
if (auto facet_value = context.insts().TryGetAs<SemIR::FacetValue>(
|
||||
self_facet_value_inst_id)) {
|
||||
inst.query_self_inst_id =
|
||||
GetCanonicalFacetOrTypeValue(context, facet_value->type_inst_id);
|
||||
} else {
|
||||
inst.query_self_inst_id = self_facet_value_inst_id;
|
||||
auto witness_id = EvalLookupSingleFinalWitness(context, SemIR::LocId(inst_id),
|
||||
inst, self_facet_value_inst_id,
|
||||
EvalImplLookupMode::Normal);
|
||||
if (witness_id == SemIR::ErrorInst::ConstantId) {
|
||||
return ConstantEvalResult::Error;
|
||||
}
|
||||
|
||||
auto result = EvalLookupSingleImplWitness(context, SemIR::LocId(inst_id),
|
||||
inst, self_facet_value_inst_id,
|
||||
EvalImplLookupMode::Normal);
|
||||
if (!result.has_value()) {
|
||||
// We use NotConstant to communicate back to impl lookup that the lookup
|
||||
// failed. This can not happen for a deferred symbolic lookup in a generic
|
||||
// eval block, since we only add the deferred lookup instruction (being
|
||||
// evaluated here) to the SemIR if the lookup succeeds.
|
||||
return ConstantEvalResult::NotConstant;
|
||||
if (witness_id.has_value()) {
|
||||
return ConstantEvalResult::Existing(witness_id);
|
||||
}
|
||||
if (result.has_final_value()) {
|
||||
return ConstantEvalResult::Existing(
|
||||
context.constant_values().Get(result.final_witness()));
|
||||
}
|
||||
|
||||
// Try again when the query is modified by a specific.
|
||||
return ConstantEvalResult::NewSamePhase(inst);
|
||||
}
|
||||
|
||||
@@ -486,6 +476,12 @@ auto EvalConstantInst(Context& context, SemIR::InterfaceDecl inst)
|
||||
context.generics().GetSelfSpecific(interface_info.generic_id)));
|
||||
}
|
||||
|
||||
auto EvalConstantInst(Context& context, SemIR::MarkInPlaceInit inst)
|
||||
-> ConstantEvalResult {
|
||||
return ConstantEvalResult::Existing(
|
||||
context.constant_values().Get(inst.src_id));
|
||||
}
|
||||
|
||||
auto EvalConstantInst(Context& context, SemIR::NamedConstraintDecl inst)
|
||||
-> ConstantEvalResult {
|
||||
const auto& named_constraint_info =
|
||||
@@ -682,12 +678,6 @@ auto EvalConstantInst(Context& /*context*/, SemIR::StructLiteral inst)
|
||||
.type_id = inst.type_id, .elements_id = inst.elements_id});
|
||||
}
|
||||
|
||||
auto EvalConstantInst(Context& /*context*/, SemIR::Temporary /*inst*/)
|
||||
-> ConstantEvalResult {
|
||||
// TODO: Handle this. Can we just return the value of `init_id`?
|
||||
return ConstantEvalResult::TODO;
|
||||
}
|
||||
|
||||
auto EvalConstantInst(Context& context, SemIR::TupleAccess inst)
|
||||
-> ConstantEvalResult {
|
||||
return PerformAggregateAccess(context, inst);
|
||||
@@ -740,8 +730,8 @@ auto EvalConstantInst(Context& context, SemIR::UnaryOperatorNot inst)
|
||||
// All other uses of unary `not` are non-constant.
|
||||
auto const_id = context.constant_values().Get(inst.operand_id);
|
||||
if (const_id.is_concrete()) {
|
||||
auto value = context.insts().GetAs<SemIR::BoolLiteral>(
|
||||
context.constant_values().GetInstId(const_id));
|
||||
auto value =
|
||||
context.constant_values().GetInstAs<SemIR::BoolLiteral>(const_id);
|
||||
value.value = SemIR::BoolValue::From(!value.value.ToBool());
|
||||
return ConstantEvalResult::NewSamePhase(value);
|
||||
}
|
||||
|
||||
@@ -403,6 +403,8 @@ auto ResolveFacetTypeRewriteConstraints(
|
||||
|
||||
auto MakePeriodSelfFacetValue(Context& context, SemIR::TypeId self_type_id)
|
||||
-> SemIR::InstId {
|
||||
CARBON_CHECK(self_type_id == SemIR::ErrorInst::TypeId ||
|
||||
context.types().Is<SemIR::FacetType>(self_type_id));
|
||||
auto entity_name_id = context.entity_names().AddCanonical({
|
||||
.name_id = SemIR::NameId::PeriodSelf,
|
||||
.parent_scope_id = context.scope_stack().PeekNameScopeId(),
|
||||
|
||||
@@ -64,8 +64,10 @@ auto ResolveFacetTypeRewriteConstraints(
|
||||
// Introduce `.Self` as a symbolic binding into the current scope, and return
|
||||
// the `SymbolicBinding` instruction.
|
||||
//
|
||||
// The `self_type_id` is either a facet type (as `FacetType`) or `type` (as
|
||||
// `TypeType`).
|
||||
// The type of `.Self` must be a `FacetType`, so that it gets wrapped in
|
||||
// `FacetAccessType` when used in a type position, such as in `U:! I(.Self)`.
|
||||
// This allows substitution with other facet values without requiring an
|
||||
// additional `FacetAccessType` to be inserted.
|
||||
auto MakePeriodSelfFacetValue(Context& context, SemIR::TypeId self_type_id)
|
||||
-> SemIR::InstId;
|
||||
|
||||
|
||||
@@ -45,14 +45,15 @@ auto AddReturnPatterns(Context& context, SemIR::LocId loc_id,
|
||||
case CARBON_KIND(SemIR::InitForm _): {
|
||||
auto pattern_type_id =
|
||||
GetPatternType(context, form_expr.type_component_id);
|
||||
auto return_slot_pattern_id = AddPatternInst<SemIR::ReturnSlotPattern>(
|
||||
context, loc_id,
|
||||
{.type_id = pattern_type_id,
|
||||
.type_inst_id = form_expr.type_component_inst_id});
|
||||
return_patterns.push_back(AddPatternInst<SemIR::OutParamPattern>(
|
||||
auto out_param_id = AddInst<SemIR::OutParamPattern>(
|
||||
context, SemIR::LocId(form_expr.form_inst_id),
|
||||
{.type_id = pattern_type_id,
|
||||
.subpattern_id = return_slot_pattern_id}));
|
||||
.pretty_name_id = SemIR::NameId::ReturnSlot});
|
||||
return_patterns.push_back(AddInst<SemIR::ReturnSlotPattern>(
|
||||
context, SemIR::LocId(form_expr.form_inst_id),
|
||||
{.type_id = pattern_type_id,
|
||||
.subpattern_id = out_param_id,
|
||||
.type_inst_id = form_expr.type_component_inst_id}));
|
||||
break;
|
||||
}
|
||||
case SemIR::ErrorInst::Kind: {
|
||||
@@ -120,13 +121,14 @@ static auto MakeFunctionSignature(Context& context, SemIR::LocId loc_id,
|
||||
|
||||
StartFunctionSignature(context);
|
||||
|
||||
// Build and add a `[ref self: Self]` parameter if needed.
|
||||
// Build and add a `self: Self` or `ref self: Self` parameter if needed.
|
||||
if (args.self_type_id.has_value()) {
|
||||
context.full_pattern_stack().StartImplicitParamList();
|
||||
|
||||
BeginSubpattern(context);
|
||||
auto self_type_region_id = EndSubpatternAsExpr(
|
||||
auto self_type_region_id = ConsumeSubpatternExpr(
|
||||
context, context.types().GetTypeInstId(args.self_type_id));
|
||||
EndEmptySubpattern(context);
|
||||
|
||||
insts.self_param_id = AddParamPattern(
|
||||
context, loc_id, SemIR::NameId::SelfValue, self_type_region_id,
|
||||
@@ -137,8 +139,8 @@ static auto MakeFunctionSignature(Context& context, SemIR::LocId loc_id,
|
||||
context.full_pattern_stack().EndImplicitParamList();
|
||||
}
|
||||
|
||||
// Build and add any explicit parameters. We always use value parameters for
|
||||
// now.
|
||||
// Build and add any explicit parameters. Whether these are references
|
||||
// or not is controlled by `args.params_are_refs`.
|
||||
context.full_pattern_stack().StartExplicitParamList();
|
||||
if (args.param_type_ids.empty()) {
|
||||
insts.param_patterns_id = SemIR::InstBlockId::Empty;
|
||||
@@ -146,12 +148,13 @@ static auto MakeFunctionSignature(Context& context, SemIR::LocId loc_id,
|
||||
context.inst_block_stack().Push();
|
||||
for (auto param_type_id : args.param_type_ids) {
|
||||
BeginSubpattern(context);
|
||||
auto param_type_region_id = EndSubpatternAsExpr(
|
||||
auto param_type_region_id = ConsumeSubpatternExpr(
|
||||
context, context.types().GetTypeInstId(param_type_id));
|
||||
EndEmptySubpattern(context);
|
||||
|
||||
context.inst_block_stack().AddInstId(AddParamPattern(
|
||||
context, loc_id, SemIR::NameId::Underscore, param_type_region_id,
|
||||
param_type_id, /*is_ref=*/false));
|
||||
param_type_id, /*is_ref=*/args.params_are_refs));
|
||||
}
|
||||
insts.param_patterns_id = context.inst_block_stack().Pop();
|
||||
}
|
||||
@@ -453,7 +456,8 @@ auto MakeFunctionDecl(Context& context, SemIR::LocId loc_id,
|
||||
SemIR::FunctionDecl function_decl = {SemIR::TypeId::None,
|
||||
SemIR::FunctionId::None, decl_block_id};
|
||||
auto decl_id = AddPlaceholderInstInNoBlock(
|
||||
context, SemIR::LocIdAndInst::UncheckedLoc(loc_id, function_decl));
|
||||
context, SemIR::LocIdAndInst::RuntimeVerified(context.sem_ir(), loc_id,
|
||||
function_decl));
|
||||
function.first_owning_decl_id = decl_id;
|
||||
if (is_definition) {
|
||||
function.definition_id = decl_id;
|
||||
|
||||
@@ -41,6 +41,8 @@ struct FunctionDeclArgs {
|
||||
bool self_is_ref = true;
|
||||
// The types of the explicit parameters.
|
||||
llvm::ArrayRef<SemIR::TypeId> param_type_ids = {};
|
||||
// Whether the parameters described by `param_type_ids` are references.
|
||||
bool params_are_refs = false;
|
||||
// The return type, or `None` if the function doesn't declare a return type.
|
||||
SemIR::TypeId return_type_id = SemIR::TypeId::None;
|
||||
};
|
||||
|
||||
+21
-11
@@ -134,10 +134,9 @@ class RebuildGenericConstantInEvalBlockCallbacks : public SubstInstCallbacks {
|
||||
// TODO: Add a function on `Context` to add the instruction without
|
||||
// inserting it into the dependent instructions list or computing a
|
||||
// constant value for it.
|
||||
// TODO: Is the location we pick here always appropriate for the new
|
||||
// instruction?
|
||||
auto inst_id = context().sem_ir().insts().AddInNoBlock(
|
||||
SemIR::LocIdAndInst::UncheckedLoc(loc_id_, new_inst));
|
||||
SemIR::LocIdAndInst::RuntimeVerified(context().sem_ir(), loc_id_,
|
||||
new_inst));
|
||||
auto const_id = AddGenericConstantInstToEvalBlock(
|
||||
context(), const_inst_id, inst_id, dependence);
|
||||
context().constant_values().Set(inst_id, const_id);
|
||||
@@ -148,8 +147,21 @@ class RebuildGenericConstantInEvalBlockCallbacks : public SubstInstCallbacks {
|
||||
|
||||
auto ReuseUnchanged(SemIR::InstId orig_inst_id) -> SemIR::InstId override {
|
||||
auto inst = context().insts().Get(orig_inst_id);
|
||||
|
||||
auto const_id = context().constant_values().Get(orig_inst_id);
|
||||
const auto& symbolic =
|
||||
context().constant_values().GetSymbolicConstant(const_id);
|
||||
// Template actions are inserted into the eval block directly, instead of
|
||||
// adding a new instruction, in `AddTemplateActionToEvalBlock`. This means
|
||||
// any instruction that is symbolic because it contains a template action as
|
||||
// an operand would Rebuild that operand with the same instruction. Then the
|
||||
// dependent instruction would be reused unchanged.
|
||||
bool is_template =
|
||||
symbolic.dependence == SemIR::ConstantDependence::Template;
|
||||
|
||||
CARBON_CHECK(
|
||||
(inst.IsOneOf<SemIR::SymbolicBinding, SemIR::SymbolicBindingPattern>()),
|
||||
is_template || (inst.IsOneOf<SemIR::SymbolicBinding,
|
||||
SemIR::SymbolicBindingPattern>()),
|
||||
"Instruction {0} has symbolic constant value but no symbolic operands",
|
||||
inst);
|
||||
|
||||
@@ -654,9 +666,7 @@ auto ResolveSpecificDecl(Context& context, SemIR::LocId loc_id,
|
||||
// recursively resolve the same specific.
|
||||
specific.decl_block_id = SemIR::InstBlockId::Empty;
|
||||
|
||||
// TODO: Store in the specific whether the declaration contains any
|
||||
// ErrorInst values.
|
||||
specific.decl_block_id =
|
||||
std::tie(specific.decl_block_id, specific.decl_block_has_error) =
|
||||
TryEvalBlockForSpecific(context, loc_id, specific_id,
|
||||
SemIR::GenericInstIndex::Region::Declaration);
|
||||
}
|
||||
@@ -722,10 +732,10 @@ auto ResolveSpecificDefinition(Context& context, SemIR::LocId loc_id,
|
||||
// The generic is not defined yet.
|
||||
return false;
|
||||
}
|
||||
// TODO: Store in the specific whether the definition contains any ErrorInst
|
||||
// values.
|
||||
specific.definition_block_id = TryEvalBlockForSpecific(
|
||||
context, loc_id, specific_id, SemIR::GenericInstIndex::Definition);
|
||||
std::tie(specific.definition_block_id,
|
||||
specific.definition_block_has_error) =
|
||||
TryEvalBlockForSpecific(context, loc_id, specific_id,
|
||||
SemIR::GenericInstIndex::Definition);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -33,8 +33,8 @@ auto HandleParseNode(Context& context, Parse::UnderscoreNameId node_id)
|
||||
}
|
||||
|
||||
// Returns the `InstKind` corresponding to the pattern's `NodeKind`.
|
||||
static auto GetPatternInstKind(Parse::NodeKind node_kind, bool is_ref)
|
||||
-> SemIR::InstKind {
|
||||
static auto GetLeafBindingPatternInstKind(Parse::NodeKind node_kind,
|
||||
bool is_ref) -> SemIR::InstKind {
|
||||
switch (node_kind) {
|
||||
case Parse::NodeKind::CompileTimeBindingPattern:
|
||||
return SemIR::InstKind::SymbolicBindingPattern;
|
||||
@@ -155,7 +155,7 @@ static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id,
|
||||
}
|
||||
|
||||
SemIR::ExprRegionId type_expr_region_id =
|
||||
EndSubpatternAsExpr(context, type_expr.inst_id);
|
||||
ConsumeSubpatternExpr(context, type_expr.inst_id);
|
||||
|
||||
// The name in a generic binding may be wrapped in `template`.
|
||||
bool is_generic = node_kind == Parse::NodeKind::CompileTimeBindingPattern;
|
||||
@@ -170,30 +170,34 @@ static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id,
|
||||
context.node_stack()
|
||||
.PopAndDiscardSoloNodeIdIf<Parse::NodeKind::RefBindingName>();
|
||||
|
||||
SemIR::InstKind pattern_inst_kind = GetPatternInstKind(node_kind, is_ref);
|
||||
|
||||
auto [name_node, name_id] = context.node_stack().PopNameWithNodeId();
|
||||
|
||||
const DeclIntroducerState& introducer =
|
||||
context.decl_introducer_state_stack().innermost();
|
||||
|
||||
auto form_id = pattern_inst_kind == SemIR::FormBindingPattern::Kind
|
||||
? context.constant_values().Get(type_expr.inst_id)
|
||||
: SemIR::ConstantId::None;
|
||||
auto form_id = node_kind == Parse::FormBindingPattern::Kind
|
||||
? type_expr.inst_id
|
||||
: SemIR::InstId::None;
|
||||
|
||||
auto make_binding_pattern = [&]() -> SemIR::InstId {
|
||||
// Adds a binding pattern for `node_id`, with the given kind and subpattern,
|
||||
// and adds its name to the current context. The subpattern must not be
|
||||
// provided unless the kind is `FormBindingPattern`.
|
||||
auto make_binding_pattern = [&](SemIR::InstKind kind,
|
||||
SemIR::InstId subpattern_id =
|
||||
SemIR::InstId::None) -> SemIR::InstId {
|
||||
// TODO: Eventually the name will need to support associations with other
|
||||
// scopes, but right now we don't support qualified names here.
|
||||
auto phase = BindingPhase::Runtime;
|
||||
if (pattern_inst_kind == SemIR::SymbolicBindingPattern::Kind) {
|
||||
if (kind == SemIR::SymbolicBindingPattern::Kind) {
|
||||
phase = is_template ? BindingPhase::Template : BindingPhase::Symbolic;
|
||||
}
|
||||
auto binding = AddBindingPattern(
|
||||
context, name_node, type_expr_region_id,
|
||||
{.kind = pattern_inst_kind,
|
||||
context, node_id, type_expr_region_id,
|
||||
{.kind = kind,
|
||||
.type_id = GetPatternType(context, type_expr.type_component_id),
|
||||
.entity_name_id = AddBindingEntityName(context, name_id, form_id,
|
||||
is_unused, phase)});
|
||||
.entity_name_id =
|
||||
AddBindingEntityName(context, name_id, form_id, is_unused, phase),
|
||||
.subpattern_id = subpattern_id});
|
||||
|
||||
// TODO: If `is_generic`, then `binding.bind_id is a SymbolicBinding. Subst
|
||||
// the `.Self` of type `type` in the `cast_type_id` type (a `FacetType`)
|
||||
@@ -281,30 +285,45 @@ static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id,
|
||||
}
|
||||
}
|
||||
|
||||
auto result_inst_id = make_binding_pattern();
|
||||
|
||||
// A binding pattern in a function signature is a `Call` parameter
|
||||
// unless it's nested inside a `var` pattern (because then the
|
||||
// enclosing `var` pattern is), or it's a compile-time binding pattern
|
||||
// (because then it's not passed to the `Call` inst).
|
||||
if (node_kind == Parse::NodeKind::LetBindingPattern ||
|
||||
node_kind == Parse::NodeKind::FormBindingPattern) {
|
||||
auto type_id = context.insts().GetAttachedType(result_inst_id);
|
||||
if (is_ref) {
|
||||
result_inst_id = AddPatternInst<SemIR::RefParamPattern>(
|
||||
context, node_id,
|
||||
{.type_id = type_id, .subpattern_id = result_inst_id});
|
||||
} else if (node_kind == Parse::NodeKind::FormBindingPattern) {
|
||||
result_inst_id = AddPatternInst<SemIR::FormParamPattern>(
|
||||
context, node_id,
|
||||
{.type_id = type_id,
|
||||
.subpattern_id = result_inst_id,
|
||||
.form_id = form_id});
|
||||
} else {
|
||||
result_inst_id = AddPatternInst<SemIR::ValueParamPattern>(
|
||||
context, node_id,
|
||||
{.type_id = type_id, .subpattern_id = result_inst_id});
|
||||
auto result_inst_id = SemIR::InstId::None;
|
||||
switch (node_kind) {
|
||||
// A binding pattern in a function signature is a `Call` parameter
|
||||
// unless it's nested inside a `var` pattern (because then the
|
||||
// enclosing `var` pattern is), or it's a compile-time binding pattern
|
||||
// (because then it's not passed to the `Call` inst).
|
||||
case Parse::NodeKind::LetBindingPattern:
|
||||
case Parse::NodeKind::FormBindingPattern: {
|
||||
auto param_pattern_id = SemIR::InstId::None;
|
||||
auto pattern_type_id =
|
||||
GetPatternType(context, type_expr.type_component_id);
|
||||
if (is_ref) {
|
||||
param_pattern_id = AddInst<SemIR::RefParamPattern>(
|
||||
context, node_id,
|
||||
{.type_id = pattern_type_id, .pretty_name_id = name_id});
|
||||
} else if (node_kind == Parse::NodeKind::FormBindingPattern) {
|
||||
param_pattern_id =
|
||||
AddInst<SemIR::FormParamPattern>(context, node_id,
|
||||
{.type_id = pattern_type_id,
|
||||
.pretty_name_id = name_id,
|
||||
.form_id = form_id});
|
||||
} else {
|
||||
param_pattern_id = AddInst<SemIR::ValueParamPattern>(
|
||||
context, node_id,
|
||||
{.type_id = pattern_type_id, .pretty_name_id = name_id});
|
||||
}
|
||||
result_inst_id = make_binding_pattern(
|
||||
SemIR::WrapperBindingPattern::Kind, param_pattern_id);
|
||||
break;
|
||||
}
|
||||
case Parse::NodeKind::VarBindingPattern:
|
||||
result_inst_id = make_binding_pattern(SemIR::RefBindingPattern::Kind);
|
||||
break;
|
||||
case Parse::NodeKind::CompileTimeBindingPattern:
|
||||
result_inst_id =
|
||||
make_binding_pattern(SemIR::SymbolicBindingPattern::Kind);
|
||||
break;
|
||||
default:
|
||||
CARBON_FATAL("Unexpected node kind {0}", node_kind);
|
||||
}
|
||||
context.node_stack().Push(node_id, result_inst_id);
|
||||
break;
|
||||
@@ -333,7 +352,8 @@ static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id,
|
||||
}
|
||||
}
|
||||
|
||||
auto binding_pattern_id = make_binding_pattern();
|
||||
auto binding_pattern_id = make_binding_pattern(
|
||||
GetLeafBindingPatternInstKind(node_kind, is_ref));
|
||||
if (node_kind == Parse::NodeKind::VarBindingPattern) {
|
||||
CARBON_CHECK(!is_generic);
|
||||
|
||||
@@ -384,14 +404,7 @@ auto HandleParseNode(Context& context,
|
||||
// compile time binding. This is popped when handling the
|
||||
// CompileTimeBindingPatternId.
|
||||
context.scope_stack().PushForSameRegion();
|
||||
|
||||
// The `.Self` must have a type of `FacetType`, so that it gets wrapped in
|
||||
// `FacetAccessType` when used in a type position, such as in `U:! I(.Self)`.
|
||||
// This allows substitution with other facet values without requiring an
|
||||
// additional `FacetAccessType` to be inserted.
|
||||
auto type_id = GetEmptyFacetType(context);
|
||||
|
||||
MakePeriodSelfFacetValue(context, type_id);
|
||||
MakePeriodSelfFacetValue(context, GetEmptyFacetType(context));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -433,7 +446,9 @@ auto HandleParseNode(Context& context,
|
||||
auto [cast_type_inst_id, cast_type_id] =
|
||||
ExprAsType(context, type_node, parsed_type_id);
|
||||
|
||||
EndSubpatternAsExpr(context, cast_type_inst_id);
|
||||
auto region_id = ConsumeSubpatternExpr(context, cast_type_inst_id);
|
||||
// TODO: Should we be tracking this somewhere?
|
||||
(void)region_id;
|
||||
|
||||
auto [name_node, name_id] = context.node_stack().PopNameWithNodeId();
|
||||
|
||||
@@ -532,10 +547,6 @@ static auto MarkPatternUnused(Context& context, SemIR::InstId inst_id) -> bool {
|
||||
auto current_inst_id = worklist.pop_back_val();
|
||||
auto inst = context.insts().Get(current_inst_id);
|
||||
CARBON_KIND_SWITCH(inst) {
|
||||
case CARBON_KIND_ANY(SemIR::AnyParamPattern, param): {
|
||||
worklist.push_back(param.subpattern_id);
|
||||
break;
|
||||
}
|
||||
case CARBON_KIND_ANY(SemIR::AnyBindingPattern, bind): {
|
||||
auto& name = context.entity_names().Get(bind.entity_name_id);
|
||||
name.is_unused = true;
|
||||
@@ -548,16 +559,16 @@ static auto MarkPatternUnused(Context& context, SemIR::InstId inst_id) -> bool {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CARBON_KIND_ANY(SemIR::AnyVarPattern, var): {
|
||||
worklist.push_back(var.subpattern_id);
|
||||
break;
|
||||
}
|
||||
case CARBON_KIND(SemIR::TuplePattern tuple): {
|
||||
for (auto elem_id : context.inst_blocks().Get(tuple.elements_id)) {
|
||||
worklist.push_back(elem_id);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CARBON_KIND(SemIR::VarPattern var): {
|
||||
worklist.push_back(var.subpattern_id);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -312,20 +312,28 @@ static auto IsValidEntryPointParamList(Context& context, Parse::NodeId node_id,
|
||||
continue;
|
||||
}
|
||||
|
||||
auto param =
|
||||
context.insts().TryGetAs<SemIR::ValueParamPattern>(param_pattern_id);
|
||||
if (!param) {
|
||||
// Only value parameters are supported for now.
|
||||
// Validate that this is a by-value parameter, which is represented as an
|
||||
// WrapperBindingPattern wrapping a ValueParamPattern.
|
||||
auto type_id = SemIR::TypeId::None;
|
||||
if (auto binding = context.insts().TryGetAs<SemIR::WrapperBindingPattern>(
|
||||
param_pattern_id)) {
|
||||
if (auto param_pattern =
|
||||
context.insts().TryGetAs<SemIR::ValueParamPattern>(
|
||||
binding->subpattern_id)) {
|
||||
type_id = param_pattern->type_id;
|
||||
}
|
||||
}
|
||||
if (!type_id.has_value()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (param->type_id == SemIR::ErrorInst::TypeId) {
|
||||
if (type_id == SemIR::ErrorInst::TypeId) {
|
||||
// Ignore parameters with erroneous types.
|
||||
continue;
|
||||
}
|
||||
|
||||
auto param_type_inst_id = context.types()
|
||||
.GetAs<SemIR::PatternType>(param->type_id)
|
||||
.GetAs<SemIR::PatternType>(type_id)
|
||||
.scrutinee_type_inst_id;
|
||||
switch (index) {
|
||||
case 0: {
|
||||
@@ -614,8 +622,7 @@ static auto CheckUnusedBindingsInPattern(Context& context,
|
||||
auto current_id = work_list.pop_back_val();
|
||||
auto inst = context.insts().Get(current_id);
|
||||
CARBON_KIND_SWITCH(inst) {
|
||||
case CARBON_KIND_ANY(SemIR::AnyParamPattern, param): {
|
||||
work_list.push_back(param.subpattern_id);
|
||||
case CARBON_KIND_ANY(SemIR::AnyLeafParamPattern, _): {
|
||||
break;
|
||||
}
|
||||
case CARBON_KIND_ANY(SemIR::AnyBindingPattern, bind): {
|
||||
@@ -628,9 +635,12 @@ static auto CheckUnusedBindingsInPattern(Context& context,
|
||||
"`unused` modifier on declaration");
|
||||
context.emitter().Emit(current_id, UnusedModifierOnDeclaration);
|
||||
}
|
||||
if (bind.kind == SemIR::WrapperBindingPattern::Kind) {
|
||||
work_list.push_back(bind.subpattern_id);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CARBON_KIND(SemIR::VarPattern var_pattern): {
|
||||
case CARBON_KIND_ANY(SemIR::AnyVarPattern, var_pattern): {
|
||||
work_list.push_back(var_pattern.subpattern_id);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -99,8 +99,9 @@ auto HandleParseNode(Context& /*context*/,
|
||||
return true;
|
||||
}
|
||||
|
||||
auto HandleParseNode(Context& /*context*/,
|
||||
Parse::InlineImportBodyId /*node_id*/) -> bool {
|
||||
auto HandleParseNode(Context& context, Parse::InlineImportBodyId node_id)
|
||||
-> bool {
|
||||
context.node_stack().Push(node_id);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
||||
// Exceptions. See /LICENSE for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
|
||||
#include "common/check.h"
|
||||
#include "toolchain/check/context.h"
|
||||
#include "toolchain/check/cpp/generate_ast.h"
|
||||
#include "toolchain/check/diagnostic_helpers.h"
|
||||
#include "toolchain/check/handle.h"
|
||||
#include "toolchain/check/inst.h"
|
||||
#include "toolchain/lex/token_kind.h"
|
||||
#include "toolchain/parse/node_ids.h"
|
||||
#include "toolchain/parse/typed_nodes.h"
|
||||
#include "toolchain/sem_ir/typed_insts.h"
|
||||
|
||||
namespace Carbon::Check {
|
||||
|
||||
auto HandleParseNode(Context& /*context*/,
|
||||
Parse::InlineIntroducerId /*node_id*/) -> bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto HandleParseNode(Context& context, Parse::InlineCppDeclId node_id) -> bool {
|
||||
auto body_id = context.node_stack()
|
||||
.PopForSoloNodeId<Parse::NodeKind::InlineImportBody>();
|
||||
|
||||
// If there are no Cpp imports prior to this point, the `Cpp` expression after
|
||||
// `import` will have already produced an error.
|
||||
//
|
||||
// TODO: It'd be nice to produce a clearer error saying to insert an `import
|
||||
// Cpp` in a file that uses `inline Cpp` and doesn't otherwise import anything
|
||||
// from package `Cpp`.
|
||||
if (context.constant_values().Get(
|
||||
context.node_stack().Pop<Parse::NodeKind::CppNameExpr>()) ==
|
||||
SemIR::ErrorInst::ConstantId) {
|
||||
return true;
|
||||
}
|
||||
CARBON_CHECK(context.cpp_context(), "Have `Cpp` name but no Cpp context");
|
||||
|
||||
auto string_token = context.parse_tree().node_token(body_id);
|
||||
auto string_value_id = context.tokens().GetStringLiteralValue(string_token);
|
||||
auto string_value = context.string_literal_values().Get(string_value_id);
|
||||
|
||||
if (context.scope_stack().PeekIndex() != ScopeIndex::Package) {
|
||||
CARBON_DIAGNOSTIC(InlineDeclNotAtFileScope, Error,
|
||||
"`inline Cpp` declaration not at file scope");
|
||||
context.emitter().Emit(node_id, InlineDeclNotAtFileScope);
|
||||
return true;
|
||||
}
|
||||
|
||||
InjectAstFromInlineCode(context, SemIR::LocId(body_id), string_value);
|
||||
AddInst<SemIR::InlineCppDecl>(context, node_id, {.text_id = string_value_id});
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Carbon::Check
|
||||
@@ -184,7 +184,7 @@ auto HandleParseNode(Context& context,
|
||||
context.inst_block_stack().PeekOrAdd();
|
||||
|
||||
context.inst_block_stack().Push();
|
||||
context.require_impls_stack().PushArray();
|
||||
context.require_impls_stack().Push(interface_id);
|
||||
// We use the arg stack to build the witness table type.
|
||||
context.args_type_info_stack().Push();
|
||||
|
||||
@@ -213,8 +213,8 @@ auto HandleParseNode(Context& context, Parse::InterfaceDefinitionId /*node_id*/)
|
||||
auto associated_entities_id = context.args_type_info_stack().Pop();
|
||||
|
||||
auto require_impls_block_id = context.require_impls_blocks().Add(
|
||||
context.require_impls_stack().PeekArray());
|
||||
context.require_impls_stack().PopArray();
|
||||
context.require_impls_stack().PeekTop());
|
||||
context.require_impls_stack().Pop();
|
||||
|
||||
auto& interface_info = context.interfaces().Get(interface_id);
|
||||
if (!interface_info.associated_entities_id.has_value()) {
|
||||
|
||||
@@ -105,23 +105,25 @@ auto HandleParseNode(Context& context, Parse::VariablePatternId node_id)
|
||||
return true;
|
||||
}
|
||||
|
||||
auto pattern_id = SemIR::InstId::None;
|
||||
// In a parameter list, a `var` pattern is always a single `Call` parameter,
|
||||
// even if it contains multiple binding patterns.
|
||||
switch (context.full_pattern_stack().CurrentKind()) {
|
||||
case FullPatternStack::Kind::ExplicitParamList:
|
||||
case FullPatternStack::Kind::ImplicitParamList:
|
||||
subpattern_id = AddPatternInst<SemIR::VarParamPattern>(
|
||||
pattern_id = AddInst<SemIR::VarParamPattern>(
|
||||
context, node_id,
|
||||
{.type_id = type_id, .subpattern_id = subpattern_id});
|
||||
break;
|
||||
case FullPatternStack::Kind::NameBindingDecl:
|
||||
pattern_id = AddInst<SemIR::VarPattern>(
|
||||
context, node_id,
|
||||
{.type_id = type_id, .subpattern_id = subpattern_id});
|
||||
break;
|
||||
case FullPatternStack::Kind::NotInEitherParamList:
|
||||
CARBON_FATAL("Unreachable");
|
||||
}
|
||||
|
||||
auto pattern_id = AddPatternInst<SemIR::VarPattern>(
|
||||
context, node_id, {.type_id = type_id, .subpattern_id = subpattern_id});
|
||||
context.node_stack().Push(node_id, pattern_id);
|
||||
return true;
|
||||
}
|
||||
@@ -129,6 +131,7 @@ auto HandleParseNode(Context& context, Parse::VariablePatternId node_id)
|
||||
// Handle the end of the full-pattern of a let/var declaration (before the
|
||||
// start of the initializer, if any).
|
||||
static auto EndFullPattern(Context& context) -> void {
|
||||
EndSubpattern(context, context.node_stack());
|
||||
auto scope_id = context.scope_stack().PeekNameScopeId();
|
||||
if (scope_id.has_value() &&
|
||||
context.name_scopes().Get(scope_id).is_interface_definition()) {
|
||||
@@ -177,10 +180,13 @@ auto HandleParseNode(Context& context, Parse::LetInitializerId node_id)
|
||||
|
||||
auto HandleParseNode(Context& context,
|
||||
Parse::AssociatedConstantInitializerId node_id) -> bool {
|
||||
EndFullPattern(context);
|
||||
auto interface_decl =
|
||||
context.scope_stack().GetCurrentScopeAs<SemIR::InterfaceWithSelfDecl>();
|
||||
EndAssociatedConstantDeclRegion(context, interface_decl.interface_id);
|
||||
return HandleInitializer(context, node_id);
|
||||
context.node_stack().Push(node_id);
|
||||
StartPatternInitializer(context);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto HandleParseNode(Context& context, Parse::VariableInitializerId node_id)
|
||||
@@ -250,6 +256,8 @@ static auto HandleDecl(Context& context, Parse::NodeId node_id) -> DeclInfo {
|
||||
context.node_stack().PopAndDiscardSoloNodeId<InitializerNodeKind>();
|
||||
EndPatternInitializer(context);
|
||||
} else {
|
||||
EndFullPattern(context);
|
||||
|
||||
// For an associated constant declaration, handle the completed declaration
|
||||
// now. We will have done this at the `=` if there was an initializer.
|
||||
if constexpr (IntroducerNodeKind ==
|
||||
@@ -260,8 +268,6 @@ static auto HandleDecl(Context& context, Parse::NodeId node_id) -> DeclInfo {
|
||||
EndAssociatedConstantDeclRegion(context, interface_decl.interface_id);
|
||||
}
|
||||
|
||||
EndFullPattern(context);
|
||||
|
||||
// A variable declaration without an explicit initializer is initialized by
|
||||
// calling `(T as Core.DefaultOrUnformed).Op()`.
|
||||
if constexpr (IntroducerNodeKind == Parse::NodeKind::VariableIntroducer) {
|
||||
|
||||
@@ -90,7 +90,7 @@ static auto HandleIntOrUnsignedIntTypeLiteral(Context& context,
|
||||
Parse::NodeId node_id,
|
||||
SemIR::IntKind int_kind,
|
||||
IntId size_id) -> bool {
|
||||
if (!(context.ints().Get(size_id) & 3).isZero()) {
|
||||
if (!(context.ints().Get(size_id) & 7).isZero()) {
|
||||
CARBON_DIAGNOSTIC(IntWidthNotMultipleOf8, Error,
|
||||
"bit width of integer type literal must be a multiple of "
|
||||
"8; use `Core.{0:Int|UInt}({1})` instead",
|
||||
|
||||
@@ -123,6 +123,7 @@ auto HandleParseNode(Context& context, Parse::ForHeaderStartId node_id)
|
||||
}
|
||||
|
||||
auto HandleParseNode(Context& context, Parse::ForInId node_id) -> bool {
|
||||
EndSubpattern(context, context.node_stack());
|
||||
auto pattern_block_id = context.pattern_block_stack().Pop();
|
||||
AddInst<SemIR::NameBindingDecl>(context, node_id,
|
||||
{.pattern_block_id = pattern_block_id});
|
||||
|
||||
@@ -184,7 +184,7 @@ auto HandleParseNode(Context& context,
|
||||
constraint_info.body_block_with_self_id =
|
||||
context.inst_block_stack().PeekOrAdd();
|
||||
|
||||
context.require_impls_stack().PushArray();
|
||||
context.require_impls_stack().Push(named_constraint_id);
|
||||
|
||||
context.node_stack().Push(node_id, named_constraint_id);
|
||||
return true;
|
||||
@@ -199,8 +199,8 @@ auto HandleParseNode(Context& context,
|
||||
context.inst_block_stack().Pop();
|
||||
|
||||
auto require_impls_block_id = context.require_impls_blocks().Add(
|
||||
context.require_impls_stack().PeekArray());
|
||||
context.require_impls_stack().PopArray();
|
||||
context.require_impls_stack().PeekTop());
|
||||
context.require_impls_stack().Pop();
|
||||
|
||||
auto& constraint_info = context.named_constraints().Get(named_constraint_id);
|
||||
if (!constraint_info.complete) {
|
||||
|
||||
@@ -43,7 +43,9 @@ static auto HandleParamListEnd(Context& context, Parse::NodeId node_id,
|
||||
if (context.node_stack().PeekIs(start_kind)) {
|
||||
// End the subpattern started by a trailing comma, or the opening delimiter
|
||||
// of an empty list.
|
||||
EndSubpatternAsNonExpr(context);
|
||||
EndEmptySubpattern(context);
|
||||
} else {
|
||||
EndSubpattern(context, context.node_stack());
|
||||
}
|
||||
// Note the Start node remains on the stack, where the param list handler can
|
||||
// make use of it.
|
||||
@@ -66,11 +68,23 @@ auto HandleParseNode(Context& context, Parse::ExplicitParamListId node_id)
|
||||
Parse::NodeKind::ExplicitParamListStart);
|
||||
}
|
||||
|
||||
auto HandleParseNode(Context& context, Parse::ParenPatternId node_id) -> bool {
|
||||
EndSubpattern(context, context.node_stack());
|
||||
auto pattern_id = context.node_stack().PopPattern();
|
||||
context.param_and_arg_refs_stack().PopAndDiscard();
|
||||
context.node_stack()
|
||||
.PopAndDiscardSoloNodeId<Parse::NodeKind::TuplePatternStart>();
|
||||
context.node_stack().Push(node_id, pattern_id);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto HandleParseNode(Context& context, Parse::TuplePatternId node_id) -> bool {
|
||||
if (context.node_stack().PeekIs(Parse::NodeKind::TuplePatternStart)) {
|
||||
// End the subpattern started by a trailing comma, or the opening delimiter
|
||||
// of an empty list.
|
||||
EndSubpatternAsNonExpr(context);
|
||||
EndEmptySubpattern(context);
|
||||
} else {
|
||||
EndSubpattern(context, context.node_stack());
|
||||
}
|
||||
auto refs_id = context.param_and_arg_refs_stack().EndAndPop(
|
||||
Parse::NodeKind::TuplePatternStart);
|
||||
@@ -88,14 +102,14 @@ auto HandleParseNode(Context& context, Parse::TuplePatternId node_id) -> bool {
|
||||
auto type_id = GetPatternType(context, GetTupleType(context, type_inst_ids));
|
||||
context.node_stack().Push(
|
||||
node_id,
|
||||
AddPatternInst<SemIR::TuplePattern>(
|
||||
AddInst<SemIR::TuplePattern>(
|
||||
context, node_id, {.type_id = type_id, .elements_id = refs_id}));
|
||||
EndSubpatternAsNonExpr(context);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto HandleParseNode(Context& context, Parse::PatternListCommaId /*node_id*/)
|
||||
-> bool {
|
||||
EndSubpattern(context, context.node_stack());
|
||||
context.param_and_arg_refs_stack().ApplyComma();
|
||||
BeginSubpattern(context);
|
||||
return true;
|
||||
|
||||
@@ -117,8 +117,8 @@ static auto TypeStructureReferencesSelf(
|
||||
// Don't generate more diagnostics.
|
||||
return true;
|
||||
}
|
||||
case CARBON_KIND(SemIR::TypeIterator::Step::SymbolicBinding bind): {
|
||||
if (context.entity_names().Get(bind.entity_name_id).name_id ==
|
||||
case CARBON_KIND(SemIR::TypeIterator::Step::SymbolicType symbolic): {
|
||||
if (context.entity_names().Get(symbolic.entity_name_id).name_id ==
|
||||
SemIR::NameId::SelfType) {
|
||||
return true;
|
||||
}
|
||||
@@ -222,9 +222,13 @@ static auto ValidateRequire(Context& context, SemIR::LocId loc_id,
|
||||
"facet type has constraints that we don't handle yet");
|
||||
return std::nullopt;
|
||||
}
|
||||
auto named_constraints_from_type_impls = llvm::map_range(
|
||||
constraint_facet_type_info.type_impls_named_constraints,
|
||||
[](auto impls) { return impls.specific_named_constraint; });
|
||||
auto named_constraints = llvm::concat<const SemIR::SpecificNamedConstraint>(
|
||||
constraint_facet_type_info.extend_named_constraints,
|
||||
constraint_facet_type_info.self_impls_named_constraints);
|
||||
constraint_facet_type_info.self_impls_named_constraints,
|
||||
named_constraints_from_type_impls);
|
||||
for (auto c : named_constraints) {
|
||||
if (c.named_constraint_id == named_constraint->named_constraint_id) {
|
||||
const auto& named_constraint =
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
// Exceptions. See /LICENSE for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
|
||||
#include "toolchain/base/kind_switch.h"
|
||||
#include "toolchain/check/context.h"
|
||||
#include "toolchain/check/convert.h"
|
||||
#include "toolchain/check/facet_type.h"
|
||||
#include "toolchain/check/generic.h"
|
||||
#include "toolchain/check/handle.h"
|
||||
#include "toolchain/check/inst.h"
|
||||
#include "toolchain/check/subst.h"
|
||||
#include "toolchain/check/type.h"
|
||||
#include "toolchain/check/unused.h"
|
||||
#include "toolchain/sem_ir/facet_type_info.h"
|
||||
@@ -39,14 +41,20 @@ auto HandleParseNode(Context& context, Parse::WhereOperandId node_id) -> bool {
|
||||
// Any references to `.Self` in constraints for the current `WhereExpr` will
|
||||
// not see constraints in the `Self` facet type, but they will resolve to
|
||||
// values through the constraints explicitly when they are combined together.
|
||||
auto self_without_constraints_type_id = self_with_constraints_type_id;
|
||||
if (auto facet_type = context.types().TryGetAs<SemIR::FacetType>(
|
||||
self_without_constraints_type_id)) {
|
||||
auto period_self_type_id = self_with_constraints_type_id;
|
||||
if (auto facet_type =
|
||||
context.types().TryGetAs<SemIR::FacetType>(period_self_type_id)) {
|
||||
const auto& info = context.facet_types().Get(facet_type->facet_type_id);
|
||||
auto stripped_info =
|
||||
SemIR::FacetTypeInfo{.extend_constraints = info.extend_constraints};
|
||||
auto stripped_info = SemIR::FacetTypeInfo::ExtendedOnly(info);
|
||||
stripped_info.Canonicalize();
|
||||
self_without_constraints_type_id = GetFacetType(context, stripped_info);
|
||||
period_self_type_id = GetFacetType(context, stripped_info);
|
||||
} else if (period_self_type_id == SemIR::TypeType::TypeId) {
|
||||
// The self may be `TypeType` in `type where X impls Y`, so we use an empty
|
||||
// facet type.
|
||||
period_self_type_id = GetEmptyFacetType(context);
|
||||
} else {
|
||||
CARBON_CHECK(period_self_type_id == SemIR::ErrorInst::TypeId,
|
||||
"unexpected .Self type {0}", period_self_type_id);
|
||||
}
|
||||
|
||||
// Introduce a name scope so that we can remove the `.Self` entry we are
|
||||
@@ -55,7 +63,7 @@ auto HandleParseNode(Context& context, Parse::WhereOperandId node_id) -> bool {
|
||||
// Introduce `.Self` as a symbolic binding. Its type is the value of the
|
||||
// expression to the left of `where`, so `MyInterface` in the example above.
|
||||
auto period_self_inst_id =
|
||||
MakePeriodSelfFacetValue(context, self_without_constraints_type_id);
|
||||
MakePeriodSelfFacetValue(context, period_self_type_id);
|
||||
|
||||
// Save the `.Self` symbolic binding on the node stack. It will become the
|
||||
// first argument to the `WhereExpr` instruction.
|
||||
@@ -185,6 +193,110 @@ auto HandleParseNode(Context& /*context*/, Parse::RequirementAndId /*node_id*/)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns whether a designator (`.Self` or `.MemberName`) is present in the
|
||||
// where clause.
|
||||
static auto FindDesignator(Context& context,
|
||||
SemIR::InstBlockId requirements_block_id) -> bool {
|
||||
auto block = context.inst_blocks().GetOrEmpty(requirements_block_id);
|
||||
|
||||
llvm::SmallVector<SemIR::InstId> requirements;
|
||||
requirements.reserve(block.size() * 2);
|
||||
|
||||
// These requirement instructions don't have a constant value, but they
|
||||
// contain only canonical instructions.
|
||||
for (auto inst_id : block) {
|
||||
auto inst = context.insts().Get(inst_id);
|
||||
CARBON_KIND_SWITCH(inst) {
|
||||
case CARBON_KIND(SemIR::RequirementBaseFacetType base): {
|
||||
requirements.push_back(base.base_type_inst_id);
|
||||
break;
|
||||
}
|
||||
case CARBON_KIND(SemIR::RequirementRewrite rewrite): {
|
||||
requirements.push_back(rewrite.lhs_id);
|
||||
requirements.push_back(rewrite.rhs_id);
|
||||
break;
|
||||
}
|
||||
case CARBON_KIND(SemIR::RequirementEquivalent equiv): {
|
||||
requirements.push_back(equiv.lhs_id);
|
||||
requirements.push_back(equiv.rhs_id);
|
||||
break;
|
||||
}
|
||||
case CARBON_KIND(SemIR::RequirementImpls impls): {
|
||||
requirements.push_back(impls.lhs_id);
|
||||
requirements.push_back(impls.rhs_id);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
CARBON_CHECK(inst_id == SemIR::ErrorInst::InstId,
|
||||
"unexpected inst {0} in requirements", inst);
|
||||
}
|
||||
}
|
||||
|
||||
class SubstFindDesignator : public SubstInstCallbacks {
|
||||
public:
|
||||
explicit SubstFindDesignator(Context* context, bool* found)
|
||||
: SubstInstCallbacks(context), found_(found) {}
|
||||
|
||||
auto Subst(SemIR::InstId& inst_id) -> SubstResult override {
|
||||
if (*found_) {
|
||||
return FullySubstituted;
|
||||
}
|
||||
|
||||
// An error was diagnosed for the where clause already.
|
||||
if (inst_id == SemIR::ErrorInst::InstId) {
|
||||
*found_ = true;
|
||||
return FullySubstituted;
|
||||
}
|
||||
|
||||
// TypeType has type TypeType, avoid recursing on its type.
|
||||
if (context().insts().Is<SemIR::TypeType>(inst_id)) {
|
||||
return FullySubstituted;
|
||||
}
|
||||
|
||||
// `.MemberName` is represented as an ImplWitnessAccess through `.Self` so
|
||||
// we only need to look for `.Self` here.
|
||||
if (auto bind =
|
||||
context().insts().TryGetAs<SemIR::SymbolicBinding>(inst_id)) {
|
||||
auto entity_name = context().entity_names().Get(bind->entity_name_id);
|
||||
if (entity_name.name_id == SemIR::NameId::PeriodSelf) {
|
||||
*found_ = true;
|
||||
return FullySubstituted;
|
||||
}
|
||||
}
|
||||
// FacetAccessType of `.Self` is evaluated to SymbolicBindingType, so we
|
||||
// need to look for this instruction too, since we won't find a `.Self`
|
||||
// directly in that case.
|
||||
if (auto bind =
|
||||
context().insts().TryGetAs<SemIR::SymbolicBindingType>(inst_id)) {
|
||||
auto entity_name = context().entity_names().Get(bind->entity_name_id);
|
||||
if (entity_name.name_id == SemIR::NameId::PeriodSelf) {
|
||||
*found_ = true;
|
||||
return FullySubstituted;
|
||||
}
|
||||
}
|
||||
|
||||
return SubstOperands;
|
||||
}
|
||||
|
||||
auto Rebuild(SemIR::InstId /*orig_inst_id*/, SemIR::Inst /*new_inst*/)
|
||||
-> SemIR::InstId override {
|
||||
CARBON_FATAL("unexpected rebuild, no insts should change");
|
||||
}
|
||||
|
||||
bool* found_;
|
||||
};
|
||||
|
||||
for (auto inst_id : requirements) {
|
||||
bool found = false;
|
||||
SubstFindDesignator callbacks(&context, &found);
|
||||
SubstInst(context, inst_id, callbacks);
|
||||
if (found) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
auto HandleParseNode(Context& context, Parse::WhereExprId node_id) -> bool {
|
||||
context.rewrites_stack().pop_back();
|
||||
// Remove `PeriodSelf` from name lookup, undoing the `Push` done for the
|
||||
@@ -193,6 +305,15 @@ auto HandleParseNode(Context& context, Parse::WhereExprId node_id) -> bool {
|
||||
SemIR::InstId period_self_id =
|
||||
context.node_stack().Pop<Parse::NodeKind::WhereOperand>();
|
||||
SemIR::InstBlockId requirements_id = context.args_type_info_stack().Pop();
|
||||
|
||||
if (!FindDesignator(context, requirements_id)) {
|
||||
CARBON_DIAGNOSTIC(WhereWithoutDesignator, Error,
|
||||
"`where` clause without a designator; expected `.Self` "
|
||||
"to appear in a requirement, or a member of `.Self`");
|
||||
context.emitter().Emit(node_id, WhereWithoutDesignator);
|
||||
period_self_id = SemIR::ErrorInst::InstId;
|
||||
}
|
||||
|
||||
AddInstAndPush<SemIR::WhereExpr>(context, node_id,
|
||||
{.type_id = SemIR::TypeType::TypeId,
|
||||
.period_self_id = period_self_id,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user