Compare commits

...
Author SHA1 Message Date
Chandler Carruth 733b76efea Add a permissions restriction
Assisted-by: Antigravity with Gemini
2026-04-24 02:09:42 +00:00
Richard Smith 69d745c6d7 Don't allow merging PRs with the dependent label. 2026-04-22 20:15:37 +00:00
Chandler Carruth d6a741f208 Add bootstrapping flags to the build system (#7084)
This takes the bootstrap support that was added and makes it available
under convenient user-facing flags for while we're doing development.

For example, to build a bootstrap compiler and use it to build and run
the tests under `//common/...` you can now use:

```
bazel test --//:bootstrap_stage=1 --//:bootstrap_exec_config=true //common/...
```

This will use the stage1 bootstrap compiler, and it will build that
compiler in the exec config (so it is optimized and the above even works
when cross-building with Bazel).

Assisted-by: Antigravity with Gemini
2026-04-21 00:59:10 +00:00
Dana Jansens 1e3906177c Update the name of the bazel target to build the nightly tarball (#7080)
The target was renamed in `82fad290285baf9763132a13b1f73de1e7919074`
from `//toolchain/install:carbon_toolchain_tar_gz_rule` to
`//toolchain/install:carbon_toolchain_tar_gz`
2026-04-20 17:59:55 +00:00
Dana Jansens 5e2f693db1 Preserve ErrorInst in TryGetTypeIdForTypeConstantId (#7079)
An ErrorInst::ConstantId constant can be used as a type, and should
result in an ErrorInst::TypeId
2026-04-20 17:11:53 +00:00
David BlaikieandGeoff Romer 1cc699ddda Make heterogenous hash table lookup opt-in (#6950)
This still only works if the hash of the distinct types are identical
(so it still doesn't address the derived pointer v base pointer case -
well, not in the way we would want to address it, we could use this
change to make derived pointer and base pointer not compare equal, but
that's not very ergonomic)

I think in a follow up maybe I can use a `TranslatingKeyContext` to
translate `Derived*` to `Base*` in general.

No test coverage for this change, since it's a no-compile situation and
we don't seem to generally do no-compile tests.
    
Discovered while working on #6940

---------

Co-authored-by: Geoff Romer <gromer@google.com>
2026-04-18 01:32:39 +00:00
Geoff Romer ad0a4ea8a4 Restructure action-dependence APIs (#7074)
- Rename `ActionIsDependent` to `ActionIsPerformable` (with negated
meaning), because that name is more concrete and, um, actionable.
- Replace `OperandIsDependent` with `OperandDependence`, which returns a
`ConstantDependence` instead of a bool. We need this additional
generality for handling form actions, where we sometimes need to ask
whether something has _any_ dependence, not just whether it has template
dependence.
2026-04-17 19:03:21 +00:00
Richard Smith f91990aa87 Override Clang class layout for Carbon class types. (#7071)
Use the Carbon-determined size and alignment for Carbon-defined classes,
rather than allowing Clang to work one out for itself using the C++
rules.
2026-04-17 00:03:29 +00:00
Geoff Romer e7626f46cc Get rid of AddPatternInst (#7075)
Instead, use the inst category to select the right block stack. This
simplifies the API for adding insts, and in subsequent changes it will
enable certain inst kinds like `SpliceInst` to seamlessly function as
either procedural insts or pattern insts.
2026-04-16 23:31:31 +00:00
Geoff Romer af04d08965 Track declared form with an InstId instead of a ConstantId. (#7072)
This is mainly in order to track a location associated with the form.
2026-04-16 23:14:31 +00:00
Richard Smith a6061d975c Compute type layouts in SemIR / Check (#7066)
Instead of allowing lower to pick whatever type layout it desires,
compute the layouts of types as part of completing the type, and make
lower build types that match that representation.

For now we assume that all pointers are 64-bit, since we don't have
access to target information. We allow tail padding reuse for structs
and tuple types (and by extension, for classes, since they use structs
as their object representation), but not for arrays.

In order to build matching LLVM types, we create LLVM packed structs
where necessary, and we insert inter-field padding on the end of the
previous field so that GEP indexes still always match Carbon's
ElementIndexes.

We don't yet use the computed alignment much in LLVM IR generation -- in
particular, `alloca`s, `load`s, and `store`s should probably use the
computed type alignment, but don't.

Assisted-by: Gemini via Antigravity
2026-04-16 22:37:48 +00:00
Chandler Carruth 896338d281 Remove duplicate file and fix to include the bin directory (#7073)
Noticed this when testing the Carbon toolchain with a more complex
environment, don't have any way to observe this at the moment in Bazel
though.

Assisted-by: Antigravity with Gemini
2026-04-16 21:42:06 +00:00
Chandler Carruth d8fe95cccb Test and fix make-variable expansion in our toolchains (#7070)
This worked correctly in the system Clang toolchain, but was not
configured correctly in the Carbon toolchains. The test is designed to
let us cover all of these.

Assisted-by: Antigravity with Gemini
2026-04-16 21:08:37 +00:00
Geoff Romer df33276f6b Revert accidental change from #7063 (#7069) 2026-04-16 20:31:54 +00:00
Chandler Carruth 327cb2396a Add a Bazel skill (#7061)
Hopefully this significantly reduces how often agents try to run `bazel`
directly without repeatedly including that in prompts. Also tried to
generally give useful skills for building, testing, and running things.

Also added a specific admonition to the `AGENTS.md` as there is a chance
that agents don't think they need to look at any skills for "standard"
build system commands like `bazel`, as those are "trivial". It seems
like a small chunk of context to spend to avoid churning with bad build
commands.

Assisted-by: Antigravity with Gemini
2026-04-16 18:58:34 +00:00
Richard Smith 1d5113649b Allow non-constant calls to constexpr functions. (#7067)
These turn up frequently in real-world code, for example when converting
a mutable global `Cpp.std.string_view` to a `Cpp.std.string`. Only
reject a non-constant call if the callee is `consteval`, not if it's
`constexpr`.
2026-04-16 17:10:48 +00:00
Chandler Carruth cdfa57f230 Pull in a fix to the new compile commands system (#7068)
This pulls in my PR:
https://github.com/wolfd/bazel-compile-commands/pull/3

Fixes #7065

Assisted-by: Antigravity with Gemini
2026-04-16 16:58:29 +00:00
Richard Smith 46f46a538d Preliminary reverse interop support for base classes. (#7059)
Create a Clang AST representation of the base specifier.
2026-04-16 00:29:40 +00:00
Nicholas Bishop 114cf401c2 Support C++ calling Carbon functions with non-() return type (#7051)
For calling non-`()` functions, the Carbon->Carbon thunk now takes an
extra reference parameter and writes the target function's return value
out to that parameter. (At the SemIR level this is how returns already
work, but adding this extra reference parameter is needed so that the
function is lowered correctly.) The C++ thunk now creates a local
variable to be initialized by the Carbon thunk, and then returns that
value to the original C++ caller.
2026-04-16 00:29:38 +00:00
rit 7c94878c10 fixed a crash when lowering a ref return initialized by value expression (#7049)
Restructured `else-if` into `InitForm` case so that diagnostics are
emitted correctly and does not lead to a crash.
Conversation:
https://discord.com/channels/655572317891461132/1052653651895779359/1492278198405431397

Closes #6891
2026-04-15 20:52:56 +00:00
Chandler Carruth 82fad29028 Switch to conventionally use a name parameter for a macro (#7053)
This is important to allow tools like buildozer to manipulate macro
invocation.
2026-04-15 05:18:25 +00:00
Chandler CarruthandDana Jansens 26ddada3ae Switch from Bazel platforms to build settings (#7052)
The runtimes and bootstrap Bazel logic was previously built around
defining custom Bazel platforms constrained with `constraint_settings`.
The use of platforms added significant complexity, including the need to
"save" and "restore" the original platform, and other complexity
stemming from changing the platform as a whole.

This PR switches to use the simpler tool of build settings, and
`target_settings` on the toolchain rather than platform compatibility.
This remove the entire need to save and restore the platform, and also
generally simplifies things.

This PR also fixes some bugs in the bootstrap that were hidden by the
use of platforms, such as the need to carefully manage the different
inputs to the runtimes build so that generated inputs pick up the
correct exec configuration -- the exec transition happened to do this
"automatically", but it seems better to handle explicitly. And it cleans
up an extraneous copy of `carbon_runtimes.bzl` that snuck in somehow.

Assisted-by: Antigravity with Gemini

---------

Co-authored-by: Dana Jansens <danakj@orodu.net>
2026-04-14 21:52:48 +00:00
Geoff Romer bab35c114e Remove the leading /proc/self/cwd/ from file paths in the debug info. (#7060)
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.
2026-04-14 19:34:32 +00:00
Chandler Carruth 96529e16bd Fully switch to the new compilation database system (#7057)
This has been working really well for me, is incredibly faster than the
other approach, and some commits continue to hit bugs in the old system
where files that aren't even going to be run through `clangd-tidy` end
up tripping up the execution. Hopefully all of that is resolved with the
new version.
2026-04-14 13:41:33 +00:00
Richard Smith 43867a678b Reverse interop support for type aliases. (#7043)
Allow any type that has a mapping from Carbon to C++ to be exposed to
C++ via name lookup. This also exposes the logic to export Carbon
classes to C++ to apply during type mapping, which gives very slight
support for passing Carbon types to C++ functions from Carbon, but not
really enough to sensibly test yet.

Depends on #7042.
2026-04-14 00:18:33 +00:00
Richard Smith 5b1de7633c Use the raw import ID, not the tagged ID, as an array index. (#7058)
Previously we'd create a *huge* array here as the tagged ID produced a
very large index value, and spend multiple seconds allocating it and
filling it with zeroes the first time `GetCppLocation` was called.

Reduces test runtime from 26s -> 6s wall time, 450s -> 320s total time
on my machine for `-c dbg`.
2026-04-13 23:53:25 +00:00
Richard Smith f31e1685fd Only export each class or namespace to C++ once. (#7042)
Instead of exporting a class or namespace each time a new C++ name
lookup discovers it, track that we have exported the entity on its name
scope, and if a new name lookup finds the same entity, produce the same
clang declaration.
2026-04-13 19:43:44 +00:00
Christopher Di BellaandDana Jansens 3cdb159067 consistently uses the caller's specific to get the callee's pattern type id (#7036)
`DoVarPreWorkImpl` was provided an incorrect pattern type while trying
to match `var` parameters, which caused the toolchain to crash in
`Convert`. This commit changes `DoVarPreWorkImpl`'s API so it derives
the pattern type from the work item's pattern ID, rather than relying on
an external source.

---------

Co-authored-by: Dana Jansens <danakj@orodu.net>
2026-04-13 17:41:54 +00:00
Dana Jansens 12d7574636 Include extended named constraints in the type of .Self for a where clause (#7048)
The `.Self` should see all extended constraints from the LHS of the
`where`, which is both interfaces and named constraints.
2026-04-10 18:05:22 +00:00
Dana JansensandRichard Smith cc5a42691e Add where T impls X constraints into the FacetTypeInfo (#7038)
This makes them part of the identified facet type, and we can see the
constraints as part of stringify and format output.

But this does not do enough to make them useful yet: Any `T impls X`
constraint must contain a reference to `.Self` somewhere. And `.Self`
references do not get substituted, so neither `T(.Self) impls X` and `T
impls X(.Self)` will match against an incoming facet value derived from
an `impl T(U) as X` or `impl T as X(U)`, since `U` and `.Self` are never
the same thing until `.Self` can be substituted.

Now that impl lookup runs into facet values containing `.Self` (a
symbolic binding), such as in `C(.Self)`, we were crashing assuming the
type of `.Self` is a FacetType, but it can be `type` in the case of
`type where C(.Self) impls...`. Instead, use an empty facet type for the
type of `.Self` so it is always a facet. This assists with substituting
other facets into it, without having to insert an extra FacetAccessType.
`MakePeriodSelfFacetValue()` now enforces this requirement.

---------

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
2026-04-10 14:29:36 +00:00
Chandler Carruth 19efec93be Fix missing runtimes file in the installation (#7044)
Fixes #7031

Also switches the previous symlinks test to be a more full integration
test. While a bit slow, it does seem worthwhile to have something that
tests things end-to-end, both with the prebuilt runtimes and the
on-demand runtimes. This test is already reasonably well separated from
the rest of the toolchain so incremental development shouldn't be
negatively impacted. And since we turned off ASan by default, it isn't
completely infeasibly expensive.

Assisted-by: Antigravity with Gemini
2026-04-09 21:49:19 +00:00
Chandler Carruth 3db691ecef Add ASan to post-merge CI and improve the action structure for GitHub (#7012)
Note that this will require changing the branch protections to use new
names for all of the checks and be somewhat disruptive. There aren't any
really good ways I could find of fixing this. Some options that I
explored:

- Have a single `pre-merge` workflow file that contains all of the other
workflows, splitting as much of the logic as we can into re-usable
files. This would basically merge testing, `pre-commit`, and
`clangd-tidy` checking into a single workflow file. However, it would
also delay the pre-commit suggestions action to only run once _all_ of
these finish, rather than as soon as pre-commit finishes.

- Serialize `pre-commit` and the rest of `pre-merge` to get the effect
of the above option but without the downside. Instead, the downside
would be serializing some of our actions.

- Have a single `pre-merge` workflow that triggers whenever any of the
other workflows completes, and have it check whether all the others have
completed. It will fail until it reaches that point. This requires
passing in GitHub keys to the workflow so that it can check the status
of other checks, and documentation online seems to indicate it is
sometimes flaky, I assume because of racing triggers of events or
check-status not being guaranteed consistent in the queries.

- Have a single `pre-merge` workflow that polls, waiting for all the
other workflows to finish using some Python logic. This requires
building and maintaining code to poll GitHub, keys to authorize that
polling, and handling all of the failure modes of a polling operation --
timeouts, network issues, etc.

Maybe there are others, but not sure what they look like. Suggestions
welcome here.

I'm hesitant to either delay the pre-commit suggestions or serialize
pre-commit execution. And the complexity or flakiness of the other two
options seem worse than having to re-work the branch protections each
time the naming here changes. But interested if folks think a different
direction would be better.

Assisted-by: Antigravity with Gemini
2026-04-09 20:37:23 +00:00
Dana JansensandChandler Carruth 6cc08ae6e6 Remove SymbolicBinding step in TypeIterator (#7039)
TypeIterator has both SymbolicType and SymbolicBinding and these overlap
in their meaning. Clarify the API by removing SymbolicBinding and just
using SymbolicType for `SymbolicBinding` insts and when they are
converted to `type` to make a `SymbolicBindingType` inst. Add the
EntityNameId to the SymbolicType for when it is available, when the
instruction is just a simple reference to a binding.

---------

Co-authored-by: Chandler Carruth <chandlerc@gmail.com>
2026-04-09 13:25:16 +00:00
Richard Smith ab977ee04d Bump vscode extension to 0.0.8. (#7041) 2026-04-09 00:06:39 +00:00
Richard Smith be0c07dc7e Give Carbon -> C++ thunks internal linkage. (#7040)
Also declare them `inline` since we're putting the `always_inline`
attribute on them. Use the `internal_linkage` attribute rather than
`SC_Static` since it's a more precise mechanism and matches what we do
for static member functions in reverse interop (where `SC_Static` means
something else and would not give the function internal linkage).
2026-04-08 21:14:30 +00:00
Dana Jansens b79d9adeca Avoid crashing in custom witness for FacetTypes and symbolic object representations (#7033)
The type must be complete to look for a witness for Destroy. Do this
check through type completion rather than just checking to see if the
ClassInfo says the definition is closed, since completing the type has
side effects (resolves the self specific definition).

Then look for whether the class is abstract through the CompleteTypeInfo
instead of just looking at the inheritance type on ClassInfo, like type
completion does.

Last, FacetTypes are trivially destroyed just like TypeType.
2026-04-08 20:09:03 +00:00
Richard Smith b74e0d1260 Superficial support for exporting complete class types to C++. (#7029)
We don't yet populate the bases or fields, so the class types show up as
empty classes in C++ for now. But we do allow calls to static member
functions.
2026-04-08 19:29:25 +00:00
dependabot[bot] 9f1a0c816c Bump addressable from 2.8.7 to 2.9.0 in /website in the bundler group across 1 directory (#7037)
Bumps the bundler group with 1 update in the /website directory:
[addressable](https://github.com/sporkmonger/addressable).

Updates `addressable` from 2.8.7 to 2.9.0
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/sporkmonger/addressable/blob/main/CHANGELOG.md">addressable's
changelog</a>.</em></p>
<blockquote>
<h2>Addressable 2.9.0 <!-- raw HTML omitted --></h2>
<ul>
<li>fixes ReDoS vulnerability in Addressable::Template#match (fixes
incomplete
remediation in 2.8.10)</li>
</ul>
<h2>Addressable 2.8.10 <!-- raw HTML omitted --></h2>
<ul>
<li>fixes ReDoS vulnerability in Addressable::Template#match</li>
</ul>
<h2>Addressable 2.8.9 <!-- raw HTML omitted --></h2>
<ul>
<li>Reduce gem size by excluding test files (<a
href="https://redirect.github.com/sporkmonger/addressable/issues/569">#569</a>)</li>
<li>No need for bundler as development dependency (<a
href="https://redirect.github.com/sporkmonger/addressable/issues/571">#571</a>,
<a
href="https://github.com/sporkmonger/addressable/commit/5fc1d93">5fc1d93</a>)</li>
<li>idna/pure: stop building the useless <code>COMPOSITION_TABLE</code>
(removes the <code>Addressable::IDNA::COMPOSITION_TABLE</code> constant)
(<a
href="https://redirect.github.com/sporkmonger/addressable/issues/564">#564</a>)</li>
</ul>
<p><a
href="https://redirect.github.com/sporkmonger/addressable/issues/569">#569</a>:
<a
href="https://redirect.github.com/sporkmonger/addressable/pull/569">sporkmonger/addressable#569</a>
<a
href="https://redirect.github.com/sporkmonger/addressable/issues/571">#571</a>:
<a
href="https://redirect.github.com/sporkmonger/addressable/pull/571">sporkmonger/addressable#571</a>
<a
href="https://redirect.github.com/sporkmonger/addressable/issues/564">#564</a>:
<a
href="https://redirect.github.com/sporkmonger/addressable/pull/564">sporkmonger/addressable#564</a></p>
<h2>Addressable 2.8.8 <!-- raw HTML omitted --></h2>
<ul>
<li>Replace the <code>unicode.data</code> blob by a ruby constant (<a
href="https://redirect.github.com/sporkmonger/addressable/issues/561">#561</a>)</li>
<li>Allow <code>public_suffix</code> 7 (<a
href="https://redirect.github.com/sporkmonger/addressable/issues/558">#558</a>)</li>
</ul>
<p><a
href="https://redirect.github.com/sporkmonger/addressable/issues/561">#561</a>:
<a
href="https://redirect.github.com/sporkmonger/addressable/pull/561">sporkmonger/addressable#561</a>
<a
href="https://redirect.github.com/sporkmonger/addressable/issues/558">#558</a>:
<a
href="https://redirect.github.com/sporkmonger/addressable/pull/558">sporkmonger/addressable#558</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/sporkmonger/addressable/commit/0c3e8589b23d4402903a9b4e1fdeba4e43c52ca4"><code>0c3e858</code></a>
Revving version and changelog</li>
<li><a
href="https://github.com/sporkmonger/addressable/commit/91915c1f7aafa3e2c9f42e2f4e21d948c7a861b8"><code>91915c1</code></a>
Fixing additional vulnerable paths</li>
<li><a
href="https://github.com/sporkmonger/addressable/commit/a091e39ff02fc321b21dea3a0df585bef2ba3744"><code>a091e39</code></a>
Add many more adversarial test cases to ensure we don't have any ReDoS
regres...</li>
<li><a
href="https://github.com/sporkmonger/addressable/commit/463a819665a3b85ce5ce894c90bd7bfa3b9d2e15"><code>463a819</code></a>
Regenerate gemspec on newer rubygems</li>
<li><a
href="https://github.com/sporkmonger/addressable/commit/0afcb0b9672bee301e5e96ed850fec05b2fcabb0"><code>0afcb0b</code></a>
Improve from O(n^2) to O(n)</li>
<li><a
href="https://github.com/sporkmonger/addressable/commit/c87f768f22ab00376ed2f8cb106f59c9d0652d3a"><code>c87f768</code></a>
Fix a ReDoS vulnerability in URI template matching</li>
<li><a
href="https://github.com/sporkmonger/addressable/commit/0d7e9b259fb0940d1a85064b04f678a7984409a5"><code>0d7e9b2</code></a>
Fix links for 2.8.9 in CHANGELOG (<a
href="https://redirect.github.com/sporkmonger/addressable/issues/573">#573</a>)</li>
<li><a
href="https://github.com/sporkmonger/addressable/commit/e2091200b31553f19248eb871f071852409796f8"><code>e209120</code></a>
Update version, gemspec, and CHANGELOG for 2.8.9 (<a
href="https://redirect.github.com/sporkmonger/addressable/issues/572">#572</a>)</li>
<li><a
href="https://github.com/sporkmonger/addressable/commit/387587492b6536748ed12a11c3fdb44a48885f28"><code>3875874</code></a>
Reduce gem size by excluding test files (<a
href="https://redirect.github.com/sporkmonger/addressable/issues/569">#569</a>)</li>
<li><a
href="https://github.com/sporkmonger/addressable/commit/3e57cc6018f94231aabb47fd341acd1b40f1e71a"><code>3e57cc6</code></a>
CI: back to <code>windows-2022</code> for MRI job</li>
<li>Additional commits viewable in <a
href="https://github.com/sporkmonger/addressable/compare/addressable-2.8.7...addressable-2.9.0">compare
view</a></li>
</ul>
</details>
<br />


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

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

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

---

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

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

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-08 19:05:36 +00:00
Geoff Romer 6f02354d62 Fix malformed split (#7035) 2026-04-08 00:31:43 +00:00
Nicholas Bishop 0635f4628f Add support for C++ calling Carbon functions with parameters (#7024)
This works by generating two thunks, one in C++ and one in Carbon. For
example, given this input:
```c++
// Carbon:
fn Callme(f: f32) {}

// C++:
void F() {
  // This will call `Callme__cpp_thunk`
  Carbon::Callme(1.0);
}
```

These functions are generated:
```c++
// Carbon:
fn Callme__carbon_thunk(ref f: f32) {
  // Call the target function.
  Callme(f);
}

// C++:

// C++ declaration for the Carbon thunk.
void Callme__carbon_thunk(float& f);

void Callme__cpp_thunk(float f) {
  // Call the Carbon thunk with args passed by reference.
  Callme__carbon_thunk(f);
}
```

For now, all arguments are passed by reference, even if they are simple
types like pointers or i32.

Functions with non-void return types are not supported yet.
2026-04-07 23:25:57 +00:00
Dana Jansens d1dc8e820d Resolve the specific definition for a function that is evaluated (#7034)
The function body may make use of values from the specific, so the
specific definition must be resolved before the function is evaluated.
2026-04-07 21:53:54 +00:00
Dana Jansens e1f30669af Remove TODO in impl lookup for discarding unused witnesses (#7032)
In #6972 we stopped finishing instructions added just for EvalOrAddInst,
which prevents adding the instruction to the containing generic eval
block.
2026-04-07 18:11:08 +00:00
Dana Jansens f483a28f2f Refactor WhereExpr evaluation into smaller helper functions (#7006)
This splits off the functionality to handle the base facet type,
rewrites, and impls constraints into separate functions.

We use the Context instead of EvalContext throughout, as the goal is to
move this code to EvalConstantInst in time. That means we do not apply
specifics to the functions in the requirements inst block. That is fine
because WhereExpr never evaluates to an WhereExpr, so this instruction
never survives as a constant value long enough to be re-evaluated with a
specific applied to it.
2026-04-07 13:52:56 +00:00
Richard Smith cc4fd39238 Support round-tripping entities through C++ and Carbon. (#7022)
Use the same C++ -> Carbon map for both interop directions, and when
importing an entity from Carbon -> C++, check whether it was originally
a C++ entity and if so return the original.

Assisted-by: Gemini via Google Antigravity
2026-04-07 08:10:21 +00:00
Chandler Carruth 013a417ea5 Add the static keyword to various syntax highlighting (#7026)
This follows #7016 which suggests using `static var` for non-instance
class data members.

Assisted-by: Antigravity with Gemini
2026-04-06 19:32:24 +00:00
Chandler Carruth 8fd4156616 Update tree_sitter for the new self syntax and static var (#7025)
This implements p7016 for tree_sitter. It also updates the build and
source file to allow this to build successfully and documents how to
successfully run these tests with Bazel given that it is fundamentally
not hermetic.

Assisted-by: Antigravity with Gemini
2026-04-06 19:31:54 +00:00
Richard Smith 05ba1d7356 Add a conversion impl from T* to const T* (#7010)
This is already allowed as a builtin conversion, but the impl allows the
generics system to know about it, so that conversions like
`Optional(T*)` to `Optional(const T*)` are allowed. This in turn allows
a C++ `T*` to be implicitly converted to a C++ `const T*` in Carbon
code.
2026-04-03 22:07:53 +00:00
Richard Smith 8e0d856725 Improve InPlaceInitializing conversion. (#7021)
Fix some situations where we'd drop the storage argument when building
an in-place initializing expression. We now guarantee that an expression
with the in-place initializing category always has a storage argument.
2026-04-03 21:28:54 +00:00
Richard SmithandGeoff Romer ea409f7cbf Fix crash lowering call to generic function with concrete type in signature (#7009)
When a function call appears in a generic, and calls another generic
that has a concrete type in its call-site signature, that concrete type
will be completed only in the file that contains the call. The generic
containing the call won't require completeness to be checked again when
forming a specific call, because the type was concrete. This means that
when lowering the call instruction, there is no single file that is
guaranteed to contain complete types for all of the callee's parameters
-- the file containing the specific callee won't necessarily have
completed the concrete parts of the signature, and the files containing
the definition and call won't necessarily have completed the symbolic
parts of the signature.

To handle this, look at both versions of the function when building its
lowered signature -- the version that we saw when forming the `call`
instruction and the version corresponding to the concrete, specific
callee, and combine information from both to form the LLVM function
type.

---------

Co-authored-by: Geoff Romer <gromer@google.com>
2026-04-03 21:24:00 +00:00
Chandler Carruth fed9e8c878 Create skills for using the gh tool (#7019)
This covers basic usage and using it to make API calls to GitHub. It
also works to establish some reasonable safety guards to avoid
inappropriate commands.

Also introduces a skill specifically for ingesting the content in GitHub
issues using the command line tool. This is especially useful as
otherwise agents may try to browse the web version of an issues that is
significantly slower and harder to ensure the agent correctly gets all
of the context into its window and is able to leverage it.

This also disables the Google documentation style checking for agent
skills, as we want to instead try to follow the conventions, phrasing,
and other patterns that map best for agents' training sets. For example,
this avoids replacing `repo` with `repository` and avoids replacing
`e.g.` with `for example`. While these replacements make lots of sense
for our human-facing documentation, the agent-facing docs probably
benefit from being terse and using the exact patterns that agents are
trained on.

Assisted-by: Antigravity with Gemini
2026-04-03 20:35:39 +00:00
Dana JansensandChandler Carruth 451b50a3ad Add storage for <type> impls <facettype> in the FacetTypeInfo (#7005)
We don't yet actually add any in check, but this adds the storage for
them, and capabilities to import them, evaluate them, substitute into
them with specifics, name them, format them, and stringify them.

---------

Co-authored-by: Chandler Carruth <chandlerc@gmail.com>
2026-04-03 00:19:57 +00:00
rit 912825feaf Add basic test for lowering choice value acquisition (#7014)
Added test for lowering choice value acquisition. Before PR #6992 the
code in issue #6862 would crash/assert saying the instruction
`AcquireValue` is not concrete. I added this test as the fix did not
have one to test this particular case.
2026-04-03 00:01:21 +00:00
Richard Smith 81ed4d829d Perform CppThunkRef conversion as part of category conversion. (#7020)
Instead of recursing back into Convert, make CppThunkRef conversion just
add an extra step to category conversion, performing a copy conversion
followed by an ephemeral reference binding conversion.
2026-04-02 23:39:25 +00:00
Jon Ross-Perkinsandjonmeow 9266ced4e3 Improve CanDestroyType to handle remaining cases (#6943)
This is only fixing the decision about *whether* to produce a witness.
Implementation of the witness is still a TODO, though where a body is
generated, it should also precisely reflect where one _needs_ to be
generated.

Note the tests:

- toolchain/lower/testdata/function/generic/import_core_witness.carbon
- toolchain/lower/testdata/function/generic/import_unused_def.carbon

These tests can probably be produced _without_ Core.Destroy, but I found
the essence of them while trying to build //examples with Core.Destroy
and a simpler minimization wasn't striking me.

Assisted-by: Google Antigravity with Gemini

---------

Co-authored-by: jonmeow <jperkins@google.com>
2026-04-02 22:54:58 +00:00
Dana Jansens 1fa7a64cd4 Add missing named constraints and self in facet type debugger dump (#7018) 2026-04-02 20:50:59 +00:00
Dana Jansens 562b423830 Always dump summaries (single line output) on bulleted details lines (#7017)
The debugger dump format looks something like

```
id: summary
 - detail 1
 - detail 2
```

But if the detail is a full Dump of some other id, then the details
start to combine and get confusing. For instance if you Dump an
interface id as the detail, you get

```
id: summary
  - interface id: summary
  - complete: yes  <-- about the interface
  - detail 2  <-- not about the interface
```

This mixes the contents of multiple Dumps and is super confusing. So
introduce DumpFooSummary for everything that is dumped on a bulleted
details line, and always use the summary version in that situation.
2026-04-02 20:19:57 +00:00
Richard Smith 6f0ec37a8b Make C++ enum types impl Core.Copy. (#7013)
Remove special-case handling in conversion logic for C++ enum types,
synthesize a custom witness of `Core.Copy` using the `primitive_copy`
builtin function.
2026-04-02 19:23:42 +00:00
Geoff Romer 0851d657c8 Add comment and test for special case in NameRef lowering (#7008) 2026-04-02 19:13:06 +00:00
Chandler Carruth 07afa07127 Fix missing include for std::log2 (#7015)
Some standard libraries require this include for the code to compile.
2026-04-02 18:27:46 +00:00
Chandler Carruth f7a767a77b Update LLVM (again) to pick up a workaround crashes the compiler when building with ASan (#7011)
Assisted-by: Antigravity with Gemini
2026-04-02 01:23:21 +00:00
Richard Smith be283a0744 Improve handling of incomplete signatures. (#7004)
Assert cleanly if we try to emit a definition or a call of a function
whose signature we were not able to emit exactly. This should make such
issues a lot easier to debug, as we were previously failing in quite
mysterious ways in this case.
2026-04-01 21:49:57 +00:00
Jon Ross-Perkins cd6ab7ce8e Switch jj settings to 'jj config set' because of repo (#7007)
jj moved the repo config outside the repo. The config.toml might exist
as a symlink in older repos (probably migration), but not clean repos.
So, overall, just switching the advice setup to make it a bit more
robust with config locations.

Also adding "trunk" to the repo config.

Assisted-by: Google Antigravity with Gemini
2026-04-01 21:36:56 +00:00
Nicholas Bishop 0075d530b9 Support const eval when calling a C++ thunk (#6947)
This makes it possible to do const eval when calling a constexpr C++
function with params and return types other than 32/64-bit integers.

Most of the new logic is in `MaybeModifyCppThunkCallForConstEval`, which
is called by `MakeConstantForCall`. This checks if the callee is a C++
thunk (using a new `SpecialFunctionKind::CppThunk` variant), and if so
it:
* Changes the callee from the C++ thunk to the thunk's callee
* Remaps parameters that are passed by pointer to the thunk to the
underlying value
* Drops the return value parameter, if present
2026-04-01 21:34:41 +00:00
Richard SmithandJon Ross-Perkins 5b7c908e8b Add documentation for setting up jj b a. (#6996)
This gives a setup where `jj b a` / `jj bookmark advance` can reliably
be used to advance a bookmark for a github pull request, without
advancing other bookmarks such as `trunk` or pointing the bookmark at an
empty commit.

---------

Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
2026-04-01 16:46:14 +00:00
Chandler Carruth 2bbcfa5e1a Remove ASan from the default build mode (#7003)
Also increases the default optimization to `-Og` which is likely to give
faster turn-around time which is what we want to optimize for here. This
should also _substantially_ shrink binary sizes, etc.

ASan is still available via `--config=asan`, and is added to the CI
infrastructure. However, my current thought is to only run it after push
rather than in PRs and in the merge queue.
2026-04-01 16:45:53 +00:00
Richard Smith dfac728571 Fix pointer sizes in debug info. (#7002)
The size is in bits, so 8 is an unlikely value. Also, don't hardcode a
size, ask the data layout for it.
2026-04-01 16:35:12 +00:00
Dana Jansens 8f3b057179 Don't qualify names after a . in an ImplWitnessAccess when stringifying (#7000)
After the `.` comes a member of the target of the ImplWitnessAccess.
It's already qualified, don't add the namespace/package to the name.
2026-04-01 15:24:57 +00:00
Dana Jansens 17180558e5 Diagnose where clause without a designator (#6995)
> We don’t allow a where constraint unless it applies a restriction to
the current type. This means referring to some
[designator](https://docs.carbon-lang.dev/docs/design/generics/details.html#kinds-of-where-constraints),
like .MemberName, or
[.Self](https://docs.carbon-lang.dev/docs/design/generics/details.html#recursive-constraints).


https://docs.carbon-lang.dev/docs/design/generics/details.html#constraints-must-use-a-designator
2026-04-01 04:16:35 +00:00
Chandler Carruth 39eac6f277 Update LLVM and fix a couple of API usages (#6998)
The `TemplateArgLocInfo` change is more interesting than usual as this
isn't enforced in the type system, and only shows up as a crash.
2026-04-01 00:00:11 +00:00
Chandler Carruth 2787089247 Switch to a Bazel-based runtimes build, and add bootstrapping (#6989)
This also switches to a more Bazel-based install layout, skipping the
FHS-based synthetic layout. The FHS-based layout is still reconstructed
explicitly when building an installable tar-ball.

The biggest change is to configure the just-built install as a Bazel
toolchain, including allowing it to build its own runtime libraries as
native Bazel libraries. This removes the need for a monolithic runtimes
build, all of that code logic is removed.

This should also pave the way to using the just-built toolchain for
doing a full 3-stage bootstrap. Building the 2nd stage is included here
as it was a particularly effective way to test that the Bazel
integration was fully working. Adding a 3rd-stage check for stability is
future work, but should be pretty easy.

There is a down-side: this uses the busybox to do the runtimes
compilation, which means they will be re-built after ~any change to
Carbon. However, the integration with Bazel should largely pay for this,
and we can continue to factor the tests away from depending on built
runtimes in most cases.

Now that we're building and testing the runtimes more directly, this
surfaced a problem with the layout of runtimes on macOS that is fixed
here. All of the Darwin OSes use a custom layout for their resource
directory compared to other targets. We now model this in both the C++
built runtimes and the Bazel built runtimes.

Assisted-by: Gemini via Antigravity
2026-03-31 23:38:03 +00:00
Richard Smith 3578dd6b91 Avoid copying Lower::FunctionInfo. (#7001)
This type is not small and contains two `SmallVector`s.
2026-03-31 22:58:42 +00:00
Richard Smith 8b59e85b16 Add support for inline Cpp declarations. (#6994)
For #6830, add support for inline C++ fragments as a declaration rather
than as a packaging directive. For now, this uses `inline Cpp
<string-literal>;` as syntax. The prior `import Cpp inline
<string-literal>;` is left alone for the time being. We can decide
separately whether to remove that.

`inline Cpp` requires that there was at least one `import Cpp`. It's not
clear to me if that's the right design long-term, but it seems
reasonable for now.

Assisted-by: Gemini via Google Antigravity
2026-03-31 22:27:43 +00:00
Geoff RomerandRichard Smith 47e9d62fd5 Model thunk call as a pattern match (#6988)
This makes the thunk-call logic more general and more supportable by
reusing the existing pattern-matching logic.

---------

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
2026-03-31 21:50:06 +00:00
Geoff Romer f4260feee4 Clean up pattern matching (#6987)
The key changes here are:
- The different kinds of pattern match are represented as alternatives
of a `variant`, instead of enumerators of an `enum`, so that they can
hold their own state instead of having a bunch of conditionally-usable
members of `MatchContext`.
- The public API of `MatchContext` is a `Match` operation that's applied
to a single pattern and scrutinee; the worklist is no longer directly
accessible.
- `Match` has a counterpart `MatchWithResult` that returns the result of
matching the pattern.
- `Context` is now a member of `MatchContext` instead of a parameter to
most of its methods.
2026-03-31 20:38:42 +00:00
Geoff Romer aec2534e9d Fix formatting of compound-type variable declarations in macros (#6997)
By default clang-format interprets function-like macro invocations as
function calls. E.g. the argument of `CARBON_KIND(llvm::ListSeparator*
sep)` is interpreted as an expression, meaning the `*` is an infix
binary operator, so it inserts a space before the `*`. This change
teaches clang-format that `CARBON_KIND(x)` and
`CARBON_ASSIGN_OR_RETURN(x)` rewrite to `x`, which is close enough to
the truth to enable it to format them correctly. See the [clang-format
docs](https://clang.llvm.org/docs/ClangFormatStyleOptions.html#macros)
for details.
2026-03-31 19:41:26 +00:00
Richard Smith 2e0d9dc709 Add syntax highlighting for SemIR. (#6958)
This applies to files named *.semir, but more interestingly also to
Carbon source lines starting `// CHECK:STDOUT:`.

Assisted-by: Gemini 3.1 Pro via Antigravity
2026-03-31 18:49:02 +00:00
Nicholas Bishop 396756c151 Handle Temporary values when const-evaling AcquireValue (#6992)
This will be used for const-evaling functions. Splitting into a separate
commit since it touches a lot of test files, and a couple fail_todo
tests are no longer failing.
2026-03-31 15:50:54 +00:00
Nicholas Bishop bf6a14ac39 Support Temporary constants (#6983)
Evaluate `Temporary` constants to a `Temporary` with the `storage` field
set to `None`.
2026-03-30 19:07:46 +00:00
Dana Jansens 9ed045ec25 Add a link to the docs on testing the toolchain to CONTRIBUTING.md (#6981)
It takes a bit of work to track down instructions on running file tests
and autoupdate. Add a link to them directly from CONTRIBUTING.md, since
all searches start there.
2026-03-28 06:21:14 +00:00
Dana Jansens 2318294eb5 Add tests for name lookup through named constraints (#6979)
Tests combinations of extend and impls in a facet type and inside a
named constraint. Name scopes are only extended if the named constraint
extends an interface, and the facet type extends the named constraint.
2026-03-28 04:15:24 +00:00
Richard Smith d9b901394b Add agent skill for producing toolchain tests. (#6986)
Assisted-by: Gemini via Google Antigravity
2026-03-28 00:48:08 +00:00
Richard Smith 181a592b8c Support for parsing expression patterns (#6977)
When parsing a pattern, if we encounter something that isn't pattern
syntax, try parsing as an expression instead. We only need one-token
lookahead to distinguish pattern syntax from expression syntax.

Track a precedence group through pattern parsing so that we can allow
different kinds of expressions in a top-level pattern (such as the
operand of `let`) and in a nested pattern (such as a subpattern of a
tuple pattern or within grouping parens). For example, we do not allow
`case if ...`, and for now I've chosen to also not allow logical or
relational operators at the top level of a pattern, so `case 1 + 1` is
OK, but `case 1 == 1` and `case true and false` require parentheses.
This decision should be ratified or revisited by a design proposal.

Very basic check support is also provided, only sufficient to form an
`ExprPattern` instruction and nothing beyond that. For now, all pattern
matching against an `ExprPattern` fails with a TODO error. To support
that, I've switched from calling `BeginSubpattern` in the parent handler
of a pattern and `EndSubpatternAs*` in the pattern handler itself to
calling both functions in parent handlers, with `EndSubpattern`
converting an expression into an expression pattern where needed.

Depends on #6976.

Assisted-by: Gemini via Google Antigravity
2026-03-28 00:06:06 +00:00
Nicholas Bishop 1ef35e8299 Fix name mangling for Carbon functions called from C++ (#6984)
Since this requires using the `Mangler` class from `toolchain/check`,
moved it from `toolchain/lower` to `toolchain/sem_ir`.

The mangled name is then attached to the `FunctionDecl` with an
`AsmLabelAttr`.
2026-03-27 22:52:11 +00:00
Richard Smith aa8e96ac72 Fix skill file to parse correctly. (#6985)
The license header needs to go after the YAML in order for it to parse.
2026-03-27 21:46:08 +00:00
Dana Jansens 5503f643c6 Introduce typed-inst accessors for ConstantValueStore (#6980)
Add `InstIs`, `GetInstAs`, and `TryGetInstAs` which act on the
underlying constant instruction in a constant value, to save an explicit
call to `GetInstId`.

```carbon
context.insts().GetAs<InstT>(context.constant_values().GetInstId(const_id))
```
can now be written as simply
```carbon
context.constant_values().GetInstAs<InstT>(const_id)
```

For future work, we might provide `GetInst()` so that
`context.insts().Get(context.constant_values().GetInstId(const_id)` can
be shortened also.
2026-03-27 21:41:04 +00:00
Richard SmithandGeoff Romer 899e54de36 Treat (pattern) as grouping parens. (#6976)
Do not treat it as a 1-tuple pattern as we used to. The design indicates
that `(pattern)` is invalid, but this appears to be an oversight, and
grouping parens appear to be the intended interpretation.

---------

Co-authored-by: Geoff Romer <gromer@google.com>
2026-03-27 21:13:57 +00:00
Jon Ross-Perkins a2ba7f1262 Have Specific track whether regions contain errors (#6982)
This removes some loops in type completion, but is motivated by the
thought that eval probably wants to query it.

Assisted-by: Google Antigravity with Gemini
2026-03-27 19:36:52 +00:00
Jon Ross-Perkins 2b1fe7c292 Small improvements to RuntimeVerified logic (#6973)
The `NodeKind` vs `InstKind` naming seems to be an old mistake.

I'm cleaning up to specific `ImportRef` handling after verifying those
were the only actual cases where inst kinds won't be compatible (or are
always compatible, depending on your point of view).

Assisted-by: Google Antigravity with Gemini
2026-03-27 19:32:12 +00:00
Dana Jansens a0416a1250 Don't finish the non-canonical instruction created in EvalOrAddInst (#6972)
EvalOrAddInst has to create a non-canonical instruction for evaluating a
few typed insts, such as LookupImplWitness which uses an InstId to
provide a location for diagnostics.

But the output of the function is a ConstantId. We do not have access to
the non-canonical InstId after the function returns. But if the constant
value was symbolic, it was being attached to the inst, and the inst
would be added to the eval block of the enclosing generic. This
needlessly added semir for a symbolic value.

The ConstantId returned by EvalOrAddInst can be used immediately, such
as to evaluate an ImplWitnessAccess. In that case, the final evaluated
result is all we need to keep in semir.

If the ConstantId needs to be replaced by specifics, it is only as part
of some other instruction, since ConstantIds themselves are not modified
by specifics, instructions are. In that case, the canonical instruction
in the constant value would have been added to some other (now symbolic)
instruction, which would be replaced by a specific.

This has no functional change, but it reduces runtime overhead and semir
output for LookupImplWitness and ImplWitnessAccess.
2026-03-27 19:29:34 +00:00
Christopher Di Bella c68c4007ab removes unused parameters from deep stack (#6971)
`best_impl_type_structure` and `best_impl_loc_id` are required to solve
the problem discussed in #6166. We don't address that issue issue yet.
Requiring them to be propagated through any function depending on
`GetFunctionId` is very tedious.

This commit removes them from `GetFunctionId` until we have a clear
design for how they should be used.
2026-03-27 16:59:25 +00:00
cui 1f7d8e4675 Fix ValueStore::GetRawIndex DCHECK to use id.index in diagnostic (#6975)
## Summary

`ValueStore::GetRawIndex` formatted the first `CARBON_DCHECK` with
`index` before the local `index` is declared. Use `id.index` so the
diagnostic matches the condition being checked.

## Test plan

- `bazelisk build //toolchain/base:base` (or `//toolchain/...` as
appropriate)
2026-03-27 12:48:46 +00:00
cui 557039648b Fix FacetTypeInfo::Print guard for self impls named constraints (#6974)
## Summary

Fixes a copy-paste bug in `FacetTypeInfo::Print`: the "self impls named
constraint" section was gated on `self_impls_constraints.empty()`
instead of `self_impls_named_constraints.empty()`.

## Test plan

- `bazelisk build //toolchain/sem_ir:sem_ir` (not run in this
environment; no Bazel installed)
2026-03-27 12:17:12 +00:00
Richard Smith 786e02cb3e clang-format: Turn off trailing commas in braced lists lint. (#6978)
Our codebase does not conform to this rule, and it's causing havoc for
automated tooling that tries to "fix" it.
2026-03-27 12:03:58 +00:00
Geoff Romer 262e24a2a0 Remove indirection through NameRefs when building a thunk call (#6965)
This reduces the SemIR size of the thunk call, and ensures that the
emitted SemIR remains correct if `pretty_name_id` is not populated.
2026-03-26 19:17:05 +00:00
Geoff Romer 18f87e4f79 Include the type in the location of binding insts (#6963) 2026-03-26 18:43:29 +00:00
Nicholas Bishop 85da6cae01 Support calling simple Carbon functions from C++ (#6967)
For now, only functions with no parameters and a `()` return type are
supported.
2026-03-26 18:14:39 +00:00
Dana Jansens d6be20641c Use earlier require decls inside a named constraint to provide witnesses for Self (#6915)
Performing a lookup against `Self` inside the definition of the named
constraint leads to cycles, as described in the document [Self
contradictions in Named
Constraints](https://docs.google.com/document/d/17rn2XmME8o2MM4OJqatSVuMa1iYZ1PAgcNrf0PXR9Q4/edit?tab=t.0).
To prevent those cycles, this change introduces a large refactoring of
impl lookup.

The impl lookup done inside eval is reduced to only performing
monomorphization. That is it:
- Only looks for an provides final witnesses.
- Is not allowed to identify the facet type of the query self.
- Returns either a final witness or None (or an error)

The paths for finding non-final witnesses are now done outside of eval,
directly in the initial `LookupImplWitness()` function. If no final
witness it found through eval, the resulting non-final
`LookupImplWitness` instruction witness is returned. It does not produce
cycles to identify the facet type of query self outside of eval, since
that does not result in repeating the identification when resolving
specifics of the named constraint or require decl.

Move the ArrayStack for Context::require_impls_stack into a new class
which tracks a NamedConstraintId (or InterfaceId) for each frame of
RequireImplsIds, so that in type completion we always can find the
correct frame for a given named constraint which is still being defined,
in order to find the RequireImplsIds in the in-progress definition.
2026-03-26 15:26:56 +00:00
Dana JansensandRichard Smith 4a0c1ddd8e Identification of a named constraint during definition (#6902)
Allow partially identifying a named constraint inside its definition,
and allow the query self in an impl lookup with a non-identified facet
type to be used to provide witnesses from that facet type. This allows
impl lookup on `Self` to find `require` decls that have been written
earlier in the named constraint, so that the named constraint to be used
to provide witnesses from inside its definition.

But disallow an incomplete named constraint from being part of an
identified facet type, to prevent forming facet values that store a
witness set that can be invalidated as the named constraint adds
interfaces to its identified facet type.

This was discussed in open discussion [on
2026-03-12](https://docs.google.com/document/d/1mjllGO3ZCL4qGt9uJHUtcxKoHAGEY7Y999ie4EtBWB8/edit?tab=t.0#heading=h.1dvbbrp5a6t3).

---------

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
2026-03-26 14:24:45 +00:00
Jon Ross-Perkins 5ae3629e49 Change GEMINI.md to AGENTS.md (#6969)
Per https://antigravity.google/changelog, supported in 1.20.5. Also note
https://agents.md

Assisted-by: Google Antigravity with Gemini
2026-03-26 01:00:13 +00:00
Jon Ross-Perkins 25b85a55ae Remove myself from SECURITY.md (#6964)
Assisted-by: Google Antigravity with Gemini
2026-03-26 00:52:16 +00:00
dependabot[bot] 15dabbfb0d Bump requests from 2.32.4 to 2.33.0 in /github_tools in the pip group across 1 directory (#6968)
Bumps the pip group with 1 update in the /github_tools directory:
[requests](https://github.com/psf/requests).

Updates `requests` from 2.32.4 to 2.33.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/psf/requests/releases">requests's
releases</a>.</em></p>
<blockquote>
<h2>v2.33.0</h2>
<h2>2.33.0 (2026-03-25)</h2>
<p><strong>Announcements</strong></p>
<ul>
<li>📣 Requests is adding inline types. If you have a typed code base
that uses Requests, please take a look at <a
href="https://redirect.github.com/psf/requests/issues/7271">#7271</a>.
Give it a try, and report any gaps or feedback you may have in the
issue. 📣</li>
</ul>
<p><strong>Security</strong></p>
<ul>
<li>CVE-2026-25645 <code>requests.utils.extract_zipped_paths</code> now
extracts contents to a non-deterministic location to prevent malicious
file replacement. This does not affect default usage of Requests, only
applications calling the utility function directly.</li>
</ul>
<p><strong>Improvements</strong></p>
<ul>
<li>Migrated to a PEP 517 build system using setuptools. (<a
href="https://redirect.github.com/psf/requests/issues/7012">#7012</a>)</li>
</ul>
<p><strong>Bugfixes</strong></p>
<ul>
<li>Fixed an issue where an empty netrc entry could cause malformed
authentication to be applied to Requests on Python 3.11+. (<a
href="https://redirect.github.com/psf/requests/issues/7205">#7205</a>)</li>
</ul>
<p><strong>Deprecations</strong></p>
<ul>
<li>Dropped support for Python 3.9 following its end of support. (<a
href="https://redirect.github.com/psf/requests/issues/7196">#7196</a>)</li>
</ul>
<p><strong>Documentation</strong></p>
<ul>
<li>Various typo fixes and doc improvements.</li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/M0d3v1"><code>@​M0d3v1</code></a> made
their first contribution in <a
href="https://redirect.github.com/psf/requests/pull/6865">psf/requests#6865</a></li>
<li><a href="https://github.com/aminvakil"><code>@​aminvakil</code></a>
made their first contribution in <a
href="https://redirect.github.com/psf/requests/pull/7220">psf/requests#7220</a></li>
<li><a href="https://github.com/E8Price"><code>@​E8Price</code></a> made
their first contribution in <a
href="https://redirect.github.com/psf/requests/pull/6960">psf/requests#6960</a></li>
<li><a href="https://github.com/mitre88"><code>@​mitre88</code></a> made
their first contribution in <a
href="https://redirect.github.com/psf/requests/pull/7244">psf/requests#7244</a></li>
<li><a href="https://github.com/magsen"><code>@​magsen</code></a> made
their first contribution in <a
href="https://redirect.github.com/psf/requests/pull/6553">psf/requests#6553</a></li>
<li><a
href="https://github.com/Rohan5commit"><code>@​Rohan5commit</code></a>
made their first contribution in <a
href="https://redirect.github.com/psf/requests/pull/7227">psf/requests#7227</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/psf/requests/blob/main/HISTORY.md#2330-2026-03-25">https://github.com/psf/requests/blob/main/HISTORY.md#2330-2026-03-25</a></p>
<h2>v2.32.5</h2>
<h2>2.32.5 (2025-08-18)</h2>
<p><strong>Bugfixes</strong></p>
<ul>
<li>The SSLContext caching feature originally introduced in 2.32.0 has
created
a new class of issues in Requests that have had negative impact across a
number
of use cases. The Requests team has decided to revert this feature as
long term
maintenance of it is proving to be unsustainable in its current
iteration.</li>
</ul>
<p><strong>Deprecations</strong></p>
<ul>
<li>Added support for Python 3.14.</li>
<li>Dropped support for Python 3.8 following its end of support.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/psf/requests/blob/main/HISTORY.md">requests's
changelog</a>.</em></p>
<blockquote>
<h2>2.33.0 (2026-03-25)</h2>
<p><strong>Announcements</strong></p>
<ul>
<li>📣 Requests is adding inline types. If you have a typed code base
that
uses Requests, please take a look at <a
href="https://redirect.github.com/psf/requests/issues/7271">#7271</a>.
Give it a try, and report
any gaps or feedback you may have in the issue. 📣</li>
</ul>
<p><strong>Security</strong></p>
<ul>
<li>CVE-2026-25645 <code>requests.utils.extract_zipped_paths</code> now
extracts
contents to a non-deterministic location to prevent malicious file
replacement. This does not affect default usage of Requests, only
applications calling the utility function directly.</li>
</ul>
<p><strong>Improvements</strong></p>
<ul>
<li>Migrated to a PEP 517 build system using setuptools. (<a
href="https://redirect.github.com/psf/requests/issues/7012">#7012</a>)</li>
</ul>
<p><strong>Bugfixes</strong></p>
<ul>
<li>Fixed an issue where an empty netrc entry could cause
malformed authentication to be applied to Requests on
Python 3.11+. (<a
href="https://redirect.github.com/psf/requests/issues/7205">#7205</a>)</li>
</ul>
<p><strong>Deprecations</strong></p>
<ul>
<li>Dropped support for Python 3.9 following its end of support. (<a
href="https://redirect.github.com/psf/requests/issues/7196">#7196</a>)</li>
</ul>
<p><strong>Documentation</strong></p>
<ul>
<li>Various typo fixes and doc improvements.</li>
</ul>
<h2>2.32.5 (2025-08-18)</h2>
<p><strong>Bugfixes</strong></p>
<ul>
<li>The SSLContext caching feature originally introduced in 2.32.0 has
created
a new class of issues in Requests that have had negative impact across a
number
of use cases. The Requests team has decided to revert this feature as
long term
maintenance of it is proving to be unsustainable in its current
iteration.</li>
</ul>
<p><strong>Deprecations</strong></p>
<ul>
<li>Added support for Python 3.14.</li>
<li>Dropped support for Python 3.8 following its end of support.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/psf/requests/commit/bc04dfd6dad4cb02cd92f5daa81eb562d280a761"><code>bc04dfd</code></a>
v2.33.0</li>
<li><a
href="https://github.com/psf/requests/commit/66d21cb07bd6255b1280291c4fafb71803cdb3b7"><code>66d21cb</code></a>
Merge commit from fork</li>
<li><a
href="https://github.com/psf/requests/commit/8b9bc8fc0f63be84602387913c4b689f19efd028"><code>8b9bc8f</code></a>
Move badges to top of README (<a
href="https://redirect.github.com/psf/requests/issues/7293">#7293</a>)</li>
<li><a
href="https://github.com/psf/requests/commit/e331a288f369973f5de0ec8901c94cae4fa87286"><code>e331a28</code></a>
Remove unused extraction call (<a
href="https://redirect.github.com/psf/requests/issues/7292">#7292</a>)</li>
<li><a
href="https://github.com/psf/requests/commit/753fd08c5eacce0aa0df73fe47e49525c67e0a29"><code>753fd08</code></a>
docs: fix FAQ grammar in httplib2 example</li>
<li><a
href="https://github.com/psf/requests/commit/774a0b837a194ee885d4fdd9ca947900cc3daf71"><code>774a0b8</code></a>
docs(socks): same block as other sections</li>
<li><a
href="https://github.com/psf/requests/commit/9c72a41bec8597f948c9d8caa5dc3f12273b3303"><code>9c72a41</code></a>
Bump github/codeql-action from 4.33.0 to 4.34.1</li>
<li><a
href="https://github.com/psf/requests/commit/ebf71906798ec82f34e07d3168f8b8aecaf8a3be"><code>ebf7190</code></a>
Bump github/codeql-action from 4.32.0 to 4.33.0</li>
<li><a
href="https://github.com/psf/requests/commit/0e4ae38f0c93d4f92a96c774bd52c069d12a4798"><code>0e4ae38</code></a>
docs: exclude Response.is_permanent_redirect from API docs (<a
href="https://redirect.github.com/psf/requests/issues/7244">#7244</a>)</li>
<li><a
href="https://github.com/psf/requests/commit/d568f47278492e630cc990a259047c67991d007a"><code>d568f47</code></a>
docs: clarify Quickstart POST example (<a
href="https://redirect.github.com/psf/requests/issues/6960">#6960</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/psf/requests/compare/v2.32.4...v2.33.0">compare
view</a></li>
</ul>
</details>
<br />


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

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

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

---

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

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

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-26 00:40:18 +00:00
Jon Ross-Perkins 25793358c3 Remove jonmeow from pre-commit config (#6966)
Removing this exception before I forget it's there

Assisted-by: Google Antigravity with Gemini
2026-03-25 23:01:20 +00:00
Richard Smith 37b238fa28 Make C++ types impl Core.Default. (#6962)
C++ classes that are default-constructible now implement `Core.Default`
by calling the default constructor.
2026-03-25 21:07:24 +00:00
Jon Ross-Perkins 311670c84a Improve vscode extension ownership (#6960)
Updates the way to access vscode marketplace for publishing. I've
adjusted CarbonInfraBot's attached email to match.

The `#editor-integrations` change is for inconsistent markdown handling
by MS...
https://marketplace.visualstudio.com/items?itemName=carbon-lang.carbon-vscode
looks fine at the moment, but I was seeing rendering as a title -- maybe
a bug that won't be rolled out, but backticks seem fair here.

Assisted-by: Google Antigravity with Gemini
2026-03-25 20:29:09 +00:00
Richard Smith 965879a9a9 Support for in-place return in eval fn. (#6954)
Ignore storage arguments when evaluating a call, like we do for other
kinds of instruction. Create a placeholder constant to represent each
out parameter so that it can be used in the function body to form more
storage arguments.

Assisted-by: Gemini 3.1 Pro via Antigravity
2026-03-25 16:03:44 +00:00
David Blaikie 415cd6f8f0 Reverse Interop: Class declarations (#6955)
Generate class declarations for Carbon classes referenced from C++

Based on #6940, review from eb62070a01
onwards
2026-03-25 04:50:08 +00:00
Richard Smith 7345f4e860 Use a per-file width for the line number gutter. (#6959)
In in the VSCode extension, use the same width for the per-split line
number gutter across all splits. This makes the visuals more consistent.
2026-03-24 21:55:48 +00:00
Richard Smith 29a8b315d3 Improve vscode line number display for test files. (#6957)
In the Carbon vscode extension, in /testdata/ files with file splits,
add a line number column within the split next to the line number column
for the overall file line number.

Assisted-by: Gemini 3.1 Pro via Antigravity
2026-03-24 21:34:03 +00:00
Jon Ross-Perkins e0305684b0 Add MakeVerifiedLocIdAndInst for runtime validation (#6942)
This follows up on a discussion about wanting to use `Any*` inst
clusters to handle boilerplate construction, with the issue that
`UncheckedLoc` use removes validation. Some context is at
https://github.com/carbon-language/carbon-lang/pull/6930#discussion_r2963157428.

This folds in `MakeImportedLocIdAndInst` because the logic is related,
particularly for `LocId` values which are `ImportIRInstId`, and it
eliminates questions of what the right function is to use.

This uncovers an error in the `NodeKind` associated with
`FormBindingPattern`. For now I'm just adding a TODO regarding that.

Assisted-by: Google Antigravity with Gemini
2026-03-24 20:56:44 +00:00
David BlaikieandJon Ross-Perkins 2af5f971da Reverse Interop: Nested namespace support (#6940)
Start recording the clang::DeclContext* -> InstId mapping for use in
later operations.

The test update includes removing the initial fail_* test because I
hadn't thought about the use of namespace aliases as a way to test for
the presence of a namespace without the failure caused by not finding
the thing inside the namespace.

---------

Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
2026-03-24 19:55:25 +00:00
Nicholas Bishop f9e1806c4a Improve diagnostics for EvaluateAsConstantExpr (#6956)
Initialize the `Diag` field `EvalResult` to get notes from clang when
`EvaluateAsConstantExpr` fails, then emit them using clang's diagnostic
infrastructure.

Also set valid source locations in a couple places, otherwise clang's
diagnostics code crashes.
2026-03-24 19:40:09 +00:00
Geoff Romer e0c6800ab3 Reverse nesting structure of parameter patterns (#6930)
See
[here](https://docs.google.com/document/d/1rWcueFwIfZox6GKVGxiUG4cBzjrZ6djXiIDGyJDtrE4/edit?tab=t.0#heading=h.7mi143mdhr2h)
for an overview of the changes and their rationale.

Assisted-by: Gemini 3.1 Pro via Antigravity
2026-03-23 20:38:20 +00:00
a345a74145 prepares vscode extension for open-vsx (#6834)
We'd like to add the Carbon vscode extension to open-vsx.org so it's
available on vscode-compatible projects (see #6766). This commit
updates documentation so that we're recommending the correct package,
and updates our dependencies to ensure users have the latest security
patches.

---------

Co-authored-by: Carbon Infra Bot <carbon-external-infra@google.com>
Co-authored-by: Chandler Carruth <chandlerc@gmail.com>
2026-03-23 16:57:54 +00:00
Chandler Carruth 86d05b598b Move the logic for preparing the installed runtimes files to its own (#6946)
directory

This cleans up the `//toolchain/install/BUILD` file and the tree
generally to be more focused on arranging the actual installation rather
than preparing inputs to that installation.

I picke `//toolchain/runtimes` so we can put other runtimes preparation
logic there, but open to any other suggested organization.

There are other runtimes things that would in theory make sense to move
such as the `prebuilt_runtimes` logic, but a subsequent PR will delete
those and so I'm leaving them where they are for now.
2026-03-23 16:39:45 +00:00
cui 7b6e3dfbb0 Fix ReadlinkSlow buffer when lstat reports zero size (#6948)
When the symlink target length from lstat was 0, the code resized the
buffer using status.size() instead of buffer_size, so the first
allocation stayed empty instead of using MinBufferSize. Align the resize
with the buffer_size path used for readlinkat.
2026-03-23 16:28:51 +00:00
Dana Jansens 17657d0586 CHECK if a SymbolicOnly instruction produces a concrete value of the same inst type (#6938)
The contract for SymbolicOnly is that the instructions are only allowed
to have a symbolic value unless their value is a different instruction
type.
2026-03-23 16:15:56 +00:00
dependabot[bot] 3ef991466d Bump flatted from 3.3.2 to 3.4.2 in /utils/vscode in the npm_and_yarn group across 1 directory (#6944)
Bumps the npm_and_yarn group with 1 update in the /utils/vscode
directory: [flatted](https://github.com/WebReflection/flatted).

Updates `flatted` from 3.3.2 to 3.4.2
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/WebReflection/flatted/commit/3bf09091c3562e17a0647bc06710dd6097079cf7"><code>3bf0909</code></a>
3.4.2</li>
<li><a
href="https://github.com/WebReflection/flatted/commit/885ddcc33cf9657caf38c57c7be45ae1c5272802"><code>885ddcc</code></a>
fix CWE-1321</li>
<li><a
href="https://github.com/WebReflection/flatted/commit/0bdba705d130f00892b1b8fcc80cf4cdea0631e3"><code>0bdba70</code></a>
added flatted-view to the benchmark</li>
<li><a
href="https://github.com/WebReflection/flatted/commit/2a02dce7c641dec31194c67663f9b0b12e62da20"><code>2a02dce</code></a>
3.4.1</li>
<li><a
href="https://github.com/WebReflection/flatted/commit/fba4e8f2e113665da275b19cd0f695f3d98e9416"><code>fba4e8f</code></a>
Merge pull request <a
href="https://redirect.github.com/WebReflection/flatted/issues/89">#89</a>
from WebReflection/python-fix</li>
<li><a
href="https://github.com/WebReflection/flatted/commit/5fe86485e6df7f7f34a07a2a85498bd3e17384e7"><code>5fe8648</code></a>
added &quot;when in Rome&quot; also a test for PHP</li>
<li><a
href="https://github.com/WebReflection/flatted/commit/53517adbefe724fe472b2f9ebcdb01910d0ae3f0"><code>53517ad</code></a>
some minor improvement</li>
<li><a
href="https://github.com/WebReflection/flatted/commit/b3e2a0c387bf446435fec45ad7f05299f012346f"><code>b3e2a0c</code></a>
Fixing recursion issue in Python too</li>
<li><a
href="https://github.com/WebReflection/flatted/commit/c4b46dbcbf782326e54ea1b65d3ebb1dc7a23fad"><code>c4b46db</code></a>
Add SECURITY.md for security policy and reporting</li>
<li><a
href="https://github.com/WebReflection/flatted/commit/f86d071e0f70de5a7d8200198824a3f07fc9c988"><code>f86d071</code></a>
Create dependabot.yml for version updates</li>
<li>Additional commits viewable in <a
href="https://github.com/WebReflection/flatted/compare/v3.3.2...v3.4.2">compare
view</a></li>
</ul>
</details>
<br />


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

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

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

---

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

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

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-23 15:09:36 +00:00
cui 44f2a68ee0 Fix iN/uN type literal width check for multiples of 8 (#6949)
The diagnostic requires bit widths to be multiples of 8, but the test
used a mask of 3 (lower two bits), which only enforces multiples of 4.
Use a mask of 7 so values like 12 incorrectly pass the check.
2026-03-22 02:03:48 +00:00
873 changed files with 40333 additions and 23124 deletions
+83
View File
@@ -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.
+171
View File
@@ -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)
+95
View File
@@ -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
+136
View File
@@ -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.
+8 -5
View File
@@ -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
+6
View File
@@ -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
+33
View File
@@ -0,0 +1,33 @@
# Part of the Carbon Language project, under the Apache License v2.0 with LLVM
# Exceptions. See /LICENSE for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
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."
-4
View File
@@ -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
+1 -1
View File
@@ -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
+23 -5
View File
@@ -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`.
+5
View File
@@ -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:
+7
View File
@@ -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
+37
View File
@@ -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
View File
@@ -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
View File
@@ -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 = [
+1 -53
View File
@@ -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=",
-1
View File
@@ -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
View File
@@ -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,
}),
)
+30
View File
@@ -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(
+59
View File
@@ -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",
)
+1 -1
View File
@@ -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}"
+2 -2
View File
@@ -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"],
)
-4
View File
@@ -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
+1 -1
View File
@@ -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) {
+37 -7
View File
@@ -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>;
})
+8
View File
@@ -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.
+1 -1
View File
@@ -45,7 +45,7 @@ interface DefaultOrUnformed {
final impl forall [T:! Default] T as DefaultOrUnformed {
fn Op() -> Self {
return T.Op();
return T.(Default.Op)();
}
}
+11
View File
@@ -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*) {
+89
View File
@@ -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:
+49 -15
View File
@@ -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
+3 -1
View File
@@ -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
+2 -3
View File
@@ -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`.
+3 -3
View File
@@ -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
+253
View File
@@ -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.
+5 -46
View File
@@ -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()
+12
View File
@@ -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
)
+1 -1
View File
@@ -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
View File
@@ -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
+2 -3
View File
@@ -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,
+3 -3
View File
@@ -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.
+4 -13
View File
@@ -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"),
+11 -11
View File
@@ -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."""
+1 -1
View File
@@ -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_) {
+5
View File
@@ -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
View File
@@ -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
View File
@@ -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);
+10 -4
View File
@@ -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,
+15 -15
View File
@@ -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(
+10 -5
View File
@@ -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_;
+3 -2
View File
@@ -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
View File
@@ -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);
+1
View File
@@ -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)
+11
View File
@@ -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
+4
View File
@@ -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_
+2 -1
View File
@@ -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);
+83 -9
View File
@@ -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 = &notes;
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
+6
View File
@@ -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_
+514
View File
@@ -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
+40
View File
@@ -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_
+414 -96
View File
@@ -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;
+10
View File
@@ -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;
+111 -74
View File
@@ -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:
+88 -71
View File
@@ -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(
+1 -1
View File
@@ -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");
+6 -5
View File
@@ -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);
+7 -6
View File
@@ -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(
+19 -20
View File
@@ -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
View File
@@ -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;
+14 -3
View File
@@ -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
View File
@@ -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.
+3 -4
View File
@@ -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
+39 -49
View File
@@ -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);
}
+2
View File
@@ -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(),
+4 -2
View File
@@ -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;
+17 -13
View File
@@ -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;
+2
View File
@@ -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
View File
@@ -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;
}
+66 -55
View File
@@ -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;
}
+19 -9
View File
@@ -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;
}
+56
View File
@@ -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
+3 -3
View File
@@ -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()) {
+12 -6
View File
@@ -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) {
+1 -1
View File
@@ -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});
+3 -3
View File
@@ -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) {
+18 -4
View File
@@ -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;
+7 -3
View File
@@ -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 =
+128 -7
View File
@@ -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