This action was created to wrap any `MetaInstId` operand of an action
instruction. This served two purposes:
1) It had a special hook in `OperandIsDependent` to allow it to be
performed while it had a dependent operand (the reference to the
instruction in the generic).
2) It created a `specific_inst` so that the downstream action saw an
instruction in the specific instead of one in the generic.
These are both replaced: the special case in `OperandIsDependent` for
`RefineInstAction` is replaced by a special case for `MetaInstId`s in
general, and the `SpecificInst` is now created as part of performing the
downstream action, rather than as a separate step carried out
beforehand.
This simplifies the produced SemIR and reduces the number of splices
significantly. It also prepares us to handle actions like
initialization, where we don't actually want to create `SpecificInst`s
immediately in the location where the action is performed, because they
actually belong somewhere else in the IR.
Assisted-by: Claude Opus 5 and Gemini via Antigravity
Add basic support for lowering templates: we can now lower `SpliceInst`
in the case where the generic and specific are from the same file (and
we don't support importing templates from other files yet in general).
In order for this to work, lowering needs to be able to query the
expression category, and to handle instructions that appear to be
(template) constants in the generic but turn out to be non-constant in
the specific, so support for that is added.
Switch `type_of_inst` from being added as an action inst to being added
as a normal inst, since it's not an action and the old approach led to a
crash in lowering.
Replace `refine_type_action` with `refine_inst_action`, and generate a
`specific_inst` instead of an `as_compatible` to represent the specific
version of an instruction that's used as an input to a template action.
This gives us a place to handle other properties of the instruction that
might vary from generic to specific beyond its type, such as its
constant value and its expression category.
For now, we provide a non-template-dependent constant value to the
`specific_inst` in addition to the non-template-dependent type we have
traditionally provided. This doesn't seem to matter for any current
actions, but sets us up to better handle future actions. The
`specific_inst` representation also allows downstream consumers of the
instruction to track which specific they should be requesting
information from. Providing a correct expression category for
`specific_inst` will be handled in a future PR.
`TemplateInst` wraps another inst. If that inst is symbolic, it is
treated as a template by `OperandDependence`.
Use this to replace `CallCppTemplateAction` with the more general
`CallAction`.
Two somewhat related fixes. The first is call-specific for now (because
it's the first action to take a `MetaInstId`), and the second is general
across all actions, but it seems like calls are the easiest place to hit
it.
1) Add support for refining inst blocks as action operands. Refine all
the insts in the block, using the appropriate InstId-derived type.
2) When an action operand is a `MetaInstId` referring to an unattached
constant, form a corresponding attached constant. This comes up when
forming (for example) an implicit `AddWith(%T)` call, where the `%T`
operand is an unattached constant.
This causes us to form correct specifics in more cases, where previously
we formed specifics that contained values that were still
template-dependent.
This unfortunately causes some existing template tests to produce more
errors, but those errors reflect cases where we were previously silently
doing the wrong thing.
Calls with template callee or args can now be deferred via an
InstAction. This allows code like this to check:
```carbon
import Cpp inline '''
template<typename T>
struct C {};
''';
fn F(generic T: type) {
let unused c: Cpp.C(T) = Cpp.C(T).C();
}
```
Example:
```carbon
import Cpp library "<vector>";
class C(T: type) {
var v: Cpp.std.vector(T);
}
inline Cpp '''
void F() {
Carbon::C<int> c;
c.v.push_back(123);
std::cout << c.v.back() << std::endl;
}
''';
```
A new `CallCppTemplateAction` is used to delay performing the C++
template call until non-symbolic arguments are known.
When a template action is created, any (non-meta) instruction operand
will refer to instructions in the corresponding generic, or possibly to
a constant. This means that when the action is eventually executed when
forming a specific, it would see the generic value for that operand
rather than the intended specific value.
Fix this by refining `InstId` operands to refer to a corresponding value
in the specific, much like we would when rebuilding a constant in the
eval block.
Generalize ConvertToValue template action to handle other kinds of
conversion target that don't perform initialization. Initializing
conversions will need more work since they also need to use a splice to
form the storage block.
Also some Bundle API tweaks:
- Remove support for non-canonical bundle IDs. Bundles don't have a
unique identity, so non-canonical bundle IDs would bloat the SemIR for
no benefit.
- Adjust the conversions between raw and typed bundle IDs to not be
templated. This makes the conversions easier to access in a debugger.
The key changes here are:
- Relocating and renaming it to align with `IdKind` (and relocating
`ToRaw` and `FromRaw` to follow it).
- Adding a `Dispatch` method that provides a generic overload-based API
for expressing per-ID-kind dispatch, and rewriting existing code to use
it.
Note in particular that using overloads instead of switch cases makes it
possible to generically handle all specializations of a templated ID
type, e.g. `SomeIdType<T>` for all `T`. We have no such templated ID
types yet, but I'm introducing one in a follow-up PR that needs this
capability.
See
[here](https://docs.google.com/document/d/1rWcueFwIfZox6GKVGxiUG4cBzjrZ6djXiIDGyJDtrE4/edit?tab=t.0)
for the design doc.
This also removes the default value of the `result_type_inst_id`
parameter of `HandleAction`, moves it before the action in the parameter
list, and documents it. This solves two problems:
- The default made it easy to forget, leading to unnecessary
`TypeOfInst` instructions.
- When it was present, putting it after the fairly "bulky" action
argument tended to make the callsite harder to read.
- 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.
#6289 absentmindedly added fields in more places, and this is undoing
that plus further fixes.
This does some cleanup of types with relation to singletons. For
`TypeType` and `ErrorInst`, they're always complete due to a
`SetComplete` call in `file.cpp`. For `CppVoidType`, it's intended to be
incomplete by construction, and so a `TypeId` should be okay. The intent
though on not generally providing these had been that `GetSingletonType`
needs to be called to get a type to be marked as complete.
In the case of `AutoType`, removing `TypeId`does change a small printing
detail. I think that's old legacy that's just been carried forward.
Otherwise, for both `InstType` and `AutoType`, I've added
`GetSingletonType` calls where they were used in order to ensure
completeness is applied correctly. These calls cause small SemIR
permutations.
This causes `AutoType` to be seen by lowering, so I'm adding a
placeholder for it. Also merging two functions that look like they're
identical in intent -- not sure why they're separate.
---------
Co-authored-by: Dana Jansens <danakj@orodu.net>
We frequently want to operate on singletons. Per discussion, drop
`Singleton` to make the code shorter.
This started off as wanting to write `inst_id.is_error()`, but the
dependency relationship between ids.h and singleton_insts.h would
require some kind of delayed evaluation to allow the implementation to
remain in headers (which I suspect is helpful to have for inlining). I
could have added something like `IsErrorInst`, forward declared in ids.h
and defined in singleton_insts.h (which would always be included by
typed_insts.h), but the template approach felt like a decent balance
between (a) removing the boilerplate `::SingletonInstId`, (b)
understandability, (c) still visually mirroring if we immediately return
a singleton, and (d) flexibility for more than just `ErrorInst`. But TBH
I'd probably still have written `is_error()` if it didn't require
addressing the cross-header cycle.
Then I tried `SemIR::InstId::Is<SemIR::ErrorInst>`, which generally
worked with types but generated the complaint that it didn't shorten
*all* singleton uses. So pulling back on `::Is`, and instead just
dropping `Singleton`.
Use TypeInstId in many more places where the instruction is required
to/known to always be a type value. This should be a somewhat exhaustive
set of places, as it covers all instructions given to
GetTypeIdFromTypeInstId().
The things of interest here are:
- Singleton instructions are always of type TypeType, so they are now
TypeInstIds.
- ErrorInst::SingletonInstId gets upcast to be an InstId because it's
sometimes used to define the type of a variable (as in `auto inst_id =
SemIR::ErrorInst::SingletonInstId;` that may hold other InstIds.
- Parse nodes don't really know about TypeInstId, so NodeStack::Push
needs to do some special casing to avoid CHECK failures when given a
TypeInstId but expecting an InstId. We leave a TODO behind here because
the nodes which are being pushed a TypeInstId should probably be taught
to expect that, but such a change is a bit tricky, so too much for this
PR.
After #5280 there are a few more typed instructions that have an `InstId
type_inst_id` that always holds a type value. These are converted to
`TypeInstId` to encode this fact in the type system. The
`ConvertAggregateElement()` function in convert.cpp is now able to
receive `TypeInstId` for a couple arguments as well.
Additionally, the `type_inst_id` field of `StructTypeField` is made into
a `TypeInstId`.
The `TupleType::elements_id` is renamed to `TupleType::type_elements_id`
to try record the fact that it's an InstBlock of type value
instructions. We don't introduce a TypeInstBlockId at this time, but it
might be nice to make blocks of TypeInstIds in the future.
To assist in working with a block of InstId that are type values, two
additional helpers are added to the TypeStore:
- GetBlockAsTypeInstIds which turns an `ArrayRef<InstId>` into a range
of `TypeInstId`
- GetBlockAsTypeIds which turns an `ArrayRef<InstId>` into a range of
`TypeId`
We use these helpers in places that iterate over the
`TupleType::type_elements_id`.
In preparation for shifting from `TypeId`s potentially representing
attached types to always representing unattached types, using
[terminology suggested on
Discord](https://discord.com/channels/655572317891461132/963846118964350976/1359286326779973712).
This change causes us to track slightly more type spelling information
through SemIR.
One change that has significant impact on the SemIR output is that we
now build a `struct_type` instruction in each class representing the
types of the fields, including the spelling used for those types. This
is now no longer always identical to the corresponding canonical
`struct_type` for the object representation, so it's built separately
and owned by the class.
Also remove `TypeBlock` support entirely, as its only use was
representing `TupleType`s, which now use an `InstBlock`.
This builds on #5212 which is adding ArgAndKind. This further modifies
CARBON_KIND_SWITCH support so that we can use it with ArgAndKind in
addition to Inst. That creates a quirk where it's easier if ArgAndKind
provides `kind` as an accessor instead of a data member, so I'm just
switching it to a class.
#5171 ran into an issue where the wrong kind was associated with an arg
(`auto arg1 = RefineOperand(context, loc_id, arg0_kind,
action.arg1());`). This PR is trying to reduce risk of similar errors by
replaced `ArgKinds()` with instead an `ArgAndKind` structure and
corresponding accessors.
A couple things I considered and discarded were:
- Adding `CARBON_KIND_SWITCH` support (in this PR -- see #5216).
- The particular way that `ForCase` works would need to change, and I
was hesitant to do that here.
- But this is why I did add `As` to `ArgAndKind`, because it had me
thinking in that direction.
- Trying to make wrapper functions like `MutateArgs(callback_fn);`. This
kind of approach gets a little messy due to some of the conditional
passes, and in particular the reverse-iteration done for `PopOperand` in
subst.cpp
- Making something like `args_and_kinds() -> std::array<ArgAndKind, 2>`.
There's one spot where iteration is already set up as a loop, but for
others it felt a little convoluted with less gain than
`MutateArgs`-style things.
I'm not sure if there's a better way to set up the table generators, I
might keep tinkering with those for ideas.
What this does:
- Adds tracking where storage is allocated.
- Determines if that storage supports destruction and, if so, records
the `destroy` function for it.
- Calls any found `destroy` functions when going out-of-scope.
What this does not do:
- Precise scope tracking of temporaries. We currently don't define
temporary scopes, which would probably be the solution.
- Destruction for anything but a `class` with `fn destroy`, in an
implicit return. That excludes:
- Classes with members that need destruction, particularly in the
absence of `fn destroy`.
- Structs, tuples, and arrays.
- Explicit returns, break, continue, nested scopes.
Noting the exclusions in particular, I think those will need work to
support, but this should set the right framework.
The cleanup block concept stems from clang and trying to share code
across cleanups, from discussion with chandlerc. Note in this
implementation I try to find `destroy` functions early on: that's so
that, when destruction is present on multiple paths, particularly
non-shared paths, we only bind the `destroy` method once.
Implementation-wise, I'll note this adds a `has_cleanup` flag to
`TemporaryStorage` and `VarStorage`. There are several related options,
but this felt similar to other information we're trying to track on
instructions. My goal with this is to mitigate the chance of accidental
calls where the storage may not be tracked for destruction. Alternatives
I considered were to not add the flag (I was worried about heightened
risk of errors), or to just add a concept for the relevant `requires`
(which just felt inconsistent).
Cleanup logic ends up in control_flow in this change because I thought
it was a reasonably consistent place for the cleanup block concept and
its pretty direct control flow interactions.