Files
carbon-lang/toolchain/check/deduce.cpp
T
Dana JansensandRichard Smith 063b9d8ca9 Deduce the FacetValue for an argument for a generic FacetType parameter (#4882)
When a function has a generic FacetType parameter, it can depend on
other FacetTypes bound as earlier parameters. To deduce the FacetValue,
we need to know the impl to attach to it, which requires knowing the
full type signature of the generic FacetType parameter. To do this,
after deducing other arguments to determine the value of non-generic
FacetType parameters, we substitute them sequentially into later
symbolic parameters to get their full facet types, then converts the
arguments to those full facet types to get the FacetValue. For example:

```
fn F(T: type, U: Interface(T));
```

Here the `T` binding is deduced to the be the caller's argument type.
But the Interface(T) can not be properly deduced to a FacetValue in the
first pass, and it will just be the caller's argument type directly.
After the first deduce pass, we will substitute the deduced T binding
into Interface(T), at which point the argument type can and will be
converted to a matching FacetValue as long as an impl can be found.

Closes #4868

This is based on PRs #4881 and #4863

---------

Co-authored-by: Richard Smith <richard@metafoo.co.uk>
2025-02-10 20:31:37 +00:00

623 lines
26 KiB
C++

// 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/deduce.h"
#include "llvm/ADT/SmallBitVector.h"
#include "toolchain/base/kind_switch.h"
#include "toolchain/check/context.h"
#include "toolchain/check/convert.h"
#include "toolchain/check/generic.h"
#include "toolchain/check/subst.h"
#include "toolchain/diagnostics/diagnostic.h"
#include "toolchain/sem_ir/ids.h"
#include "toolchain/sem_ir/impl.h"
#include "toolchain/sem_ir/typed_insts.h"
namespace Carbon::Check {
namespace {
// A list of pairs of (instruction from generic, corresponding instruction from
// call to of generic) for which we still need to perform deduction, along with
// methods to add and pop pending deductions from the list. Deductions are
// popped in order from most- to least-recently pushed, with the intent that
// they are visited in depth-first order, although the order is not expected to
// matter except when it influences which error is diagnosed.
class DeductionWorklist {
public:
explicit DeductionWorklist(Context& context) : context_(context) {}
struct PendingDeduction {
SemIR::InstId param;
SemIR::InstId arg;
bool needs_substitution;
};
// Adds a single (param, arg) deduction.
auto Add(SemIR::InstId param, SemIR::InstId arg, bool needs_substitution)
-> void {
deductions_.push_back(
{.param = param, .arg = arg, .needs_substitution = needs_substitution});
}
// Adds a single (param, arg) type deduction.
auto Add(SemIR::TypeId param, SemIR::TypeId arg, bool needs_substitution)
-> void {
Add(context_.types().GetInstId(param), context_.types().GetInstId(arg),
needs_substitution);
}
// Adds a single (param, arg) deduction of a specific.
auto Add(SemIR::SpecificId param, SemIR::SpecificId arg,
bool needs_substitution) -> void {
if (!param.has_value() || !arg.has_value()) {
return;
}
auto& param_specific = context_.specifics().Get(param);
auto& arg_specific = context_.specifics().Get(arg);
if (param_specific.generic_id != arg_specific.generic_id) {
// TODO: Decide whether to error on this or just treat the specific as
// non-deduced. For now we treat it as non-deduced.
return;
}
AddAll(param_specific.args_id, arg_specific.args_id, needs_substitution);
}
// Adds a list of (param, arg) deductions. These are added in reverse order so
// they are popped in forward order.
template <typename ElementId>
auto AddAll(llvm::ArrayRef<ElementId> params, llvm::ArrayRef<ElementId> args,
bool needs_substitution) -> void {
if (params.size() != args.size()) {
// TODO: Decide whether to error on this or just treat the parameter list
// as non-deduced. For now we treat it as non-deduced.
return;
}
for (auto [param, arg] : llvm::reverse(llvm::zip_equal(params, args))) {
Add(param, arg, needs_substitution);
}
}
auto AddAll(SemIR::InstBlockId params, llvm::ArrayRef<SemIR::InstId> args,
bool needs_substitution) -> void {
AddAll(context_.inst_blocks().Get(params), args, needs_substitution);
}
auto AddAll(SemIR::StructTypeFieldsId params, SemIR::StructTypeFieldsId args,
bool needs_substitution) -> void {
const auto& param_fields = context_.struct_type_fields().Get(params);
const auto& arg_fields = context_.struct_type_fields().Get(args);
if (param_fields.size() != arg_fields.size()) {
// TODO: Decide whether to error on this or just treat the parameter list
// as non-deduced. For now we treat it as non-deduced.
return;
}
// Don't do deduction unless the names match in order.
// TODO: Support reordering of names.
for (auto [param, arg] : llvm::zip_equal(param_fields, arg_fields)) {
if (param.name_id != arg.name_id) {
return;
}
}
for (auto [param, arg] :
llvm::reverse(llvm::zip_equal(param_fields, arg_fields))) {
Add(param.type_id, arg.type_id, needs_substitution);
}
}
auto AddAll(SemIR::InstBlockId params, SemIR::InstBlockId args,
bool needs_substitution) -> void {
AddAll(context_.inst_blocks().Get(params), context_.inst_blocks().Get(args),
needs_substitution);
}
auto AddAll(SemIR::TypeBlockId params, SemIR::TypeBlockId args,
bool needs_substitution) -> void {
AddAll(context_.type_blocks().Get(params), context_.type_blocks().Get(args),
needs_substitution);
}
auto AddAll(SemIR::FacetTypeId params, SemIR::FacetTypeId args,
bool needs_substitution) -> void {
const auto& param_impls =
context_.facet_types().Get(params).impls_constraints;
const auto& arg_impls = context_.facet_types().Get(args).impls_constraints;
// TODO: Decide whether to error on these or just treat the parameter list
// as non-deduced. For now we treat it as non-deduced.
if (param_impls.size() != 1 || arg_impls.size() != 1) {
return;
}
auto param = param_impls.front();
auto arg = arg_impls.front();
if (param.interface_id != arg.interface_id) {
return;
}
Add(param.specific_id, arg.specific_id, needs_substitution);
}
// Adds a (param, arg) pair for an instruction argument, given its kind.
auto AddInstArg(SemIR::IdKind kind, int32_t param, int32_t arg,
bool needs_substitution) -> void {
switch (kind) {
case SemIR::IdKind::None:
case SemIR::IdKind::For<SemIR::ClassId>:
case SemIR::IdKind::For<SemIR::IntKind>:
break;
case SemIR::IdKind::For<SemIR::InstId>:
Add(SemIR::InstId(param), SemIR::InstId(arg), needs_substitution);
break;
case SemIR::IdKind::For<SemIR::TypeId>:
Add(SemIR::TypeId(param), SemIR::TypeId(arg), needs_substitution);
break;
case SemIR::IdKind::For<SemIR::StructTypeFieldsId>:
AddAll(SemIR::StructTypeFieldsId(param), SemIR::StructTypeFieldsId(arg),
needs_substitution);
break;
case SemIR::IdKind::For<SemIR::InstBlockId>:
AddAll(SemIR::InstBlockId(param), SemIR::InstBlockId(arg),
needs_substitution);
break;
case SemIR::IdKind::For<SemIR::TypeBlockId>:
AddAll(SemIR::TypeBlockId(param), SemIR::TypeBlockId(arg),
needs_substitution);
break;
case SemIR::IdKind::For<SemIR::SpecificId>:
Add(SemIR::SpecificId(param), SemIR::SpecificId(arg),
needs_substitution);
break;
case SemIR::IdKind::For<SemIR::FacetTypeId>:
AddAll(SemIR::FacetTypeId(param), SemIR::FacetTypeId(arg),
needs_substitution);
break;
default:
CARBON_FATAL("unexpected argument kind");
}
}
// Returns whether we have completed all deductions.
auto Done() -> bool { return deductions_.empty(); }
// Pops the next deduction. Requires `!Done()`.
auto PopNext() -> PendingDeduction { return deductions_.pop_back_val(); }
private:
Context& context_;
llvm::SmallVector<PendingDeduction> deductions_;
};
// State that is tracked throughout the deduction process.
class DeductionContext {
public:
// Preparse to perform deduction. If an enclosing specific or self type
// are provided, adds the corresponding arguments as known arguments that will
// not be deduced.
DeductionContext(Context& context, SemIR::LocId loc_id,
SemIR::GenericId generic_id,
SemIR::SpecificId enclosing_specific_id,
SemIR::InstId self_type_id, bool diagnose);
auto context() const -> Context& { return *context_; }
// Adds a pending deduction of `param` from `arg`. `needs_substitution`
// indicates whether we need to substitute known generic parameters into
// `param`.
template <typename ParamT, typename ArgT>
auto Add(ParamT param, ArgT arg, bool needs_substitution) -> void {
worklist_.Add(param, arg, needs_substitution);
}
// Same as `Add` but for an array or block of operands.
template <typename ParamT, typename ArgT>
auto AddAll(ParamT param, ArgT arg, bool needs_substitution) -> void {
worklist_.AddAll(param, arg, needs_substitution);
}
// Performs all deductions in the deduction worklist. Returns whether
// deduction succeeded.
auto Deduce() -> bool;
// Returns whether every generic parameter has a corresponding deduced generic
// argument. If not, issues a suitable diagnostic.
auto CheckDeductionIsComplete() -> bool;
// Forms a specific corresponding to the deduced generic with the deduced
// argument list. Must not be called before deduction is complete.
auto MakeSpecific() -> SemIR::SpecificId;
private:
void NoteInitializingParam(SemIR::InstId param_id, auto& builder) {
if (auto param = context().insts().TryGetAs<SemIR::SymbolicBindingPattern>(
param_id)) {
CARBON_DIAGNOSTIC(InitializingGenericParam, Note,
"initializing generic parameter `{0}` declared here",
SemIR::NameId);
builder.Note(param_id, InitializingGenericParam,
context().entity_names().Get(param->entity_name_id).name_id);
} else {
NoteGenericHere(context(), generic_id_, builder);
}
}
Context* context_;
SemIR::LocId loc_id_;
SemIR::GenericId generic_id_;
bool diagnose_;
DeductionWorklist worklist_;
llvm::SmallVector<SemIR::InstId> result_arg_ids_;
llvm::SmallVector<Substitution> substitutions_;
SemIR::CompileTimeBindIndex first_deduced_index_;
// Non-deduced indexes, indexed by parameter index - first_deduced_index_.
llvm::SmallBitVector non_deduced_indexes_;
};
} // namespace
static auto NoteGenericHere(Context& context, SemIR::GenericId generic_id,
Context::DiagnosticBuilder& diag) -> void {
CARBON_DIAGNOSTIC(DeductionGenericHere, Note,
"while deducing parameters of generic declared here");
diag.Note(context.generics().Get(generic_id).decl_id, DeductionGenericHere);
}
DeductionContext::DeductionContext(Context& context, SemIR::LocId loc_id,
SemIR::GenericId generic_id,
SemIR::SpecificId enclosing_specific_id,
SemIR::InstId self_type_id, bool diagnose)
: context_(&context),
loc_id_(loc_id),
generic_id_(generic_id),
diagnose_(diagnose),
worklist_(context),
first_deduced_index_(0) {
CARBON_CHECK(generic_id.has_value(),
"Performing deduction for non-generic entity");
// Initialize the deduced arguments to `None`.
result_arg_ids_.resize(
context.inst_blocks()
.Get(context.generics().Get(generic_id_).bindings_id)
.size(),
SemIR::InstId::None);
if (enclosing_specific_id.has_value()) {
// Copy any outer generic arguments from the specified instance and prepare
// to substitute them into the function declaration.
auto args = context.inst_blocks().Get(
context.specifics().Get(enclosing_specific_id).args_id);
llvm::copy(args, result_arg_ids_.begin());
// TODO: Subst is linear in the length of the substitutions list. Change
// it so we can pass in an array mapping indexes to substitutions instead.
substitutions_.reserve(args.size() + result_arg_ids_.size());
for (auto [i, subst_inst_id] : llvm::enumerate(args)) {
substitutions_.push_back(
{.bind_id = SemIR::CompileTimeBindIndex(i),
.replacement_id = context.constant_values().Get(subst_inst_id)});
}
first_deduced_index_ = SemIR::CompileTimeBindIndex(args.size());
}
if (self_type_id.has_value()) {
// Copy the provided `Self` type as the value of the next binding.
auto self_index = first_deduced_index_;
result_arg_ids_[self_index.index] = self_type_id;
substitutions_.push_back(
{.bind_id = SemIR::CompileTimeBindIndex(self_index),
.replacement_id = context.constant_values().Get(self_type_id)});
first_deduced_index_ = SemIR::CompileTimeBindIndex(self_index.index + 1);
}
non_deduced_indexes_.resize(result_arg_ids_.size() -
first_deduced_index_.index);
}
auto DeductionContext::Deduce() -> bool {
while (!worklist_.Done()) {
auto [param_id, arg_id, needs_substitution] = worklist_.PopNext();
// TODO: Bail out if there's nothing to deduce: if we're not in a pattern
// and the parameter doesn't have a symbolic constant value.
auto param_type_id = context().insts().Get(param_id).type_id();
// If the parameter has a symbolic type, deduce against that.
if (param_type_id.AsConstantId().is_symbolic()) {
Add(context().types().GetInstId(param_type_id),
context().types().GetInstId(context().insts().Get(arg_id).type_id()),
needs_substitution);
} else {
// The argument (e.g. a TupleLiteral of types) may be convertible to a
// compile-time value (e.g. TupleType) that we can decompose further.
// So we do this conversion here, even though we will later try convert
// again when we have deduced all of the bindings.
DiagnosticAnnotationScope annotate_diagnostics(
&context().emitter(),
[&](auto& builder) { NoteInitializingParam(param_id, builder); });
arg_id = ConvertToValueOfType(context(), loc_id_, arg_id, param_type_id);
if (arg_id == SemIR::ErrorInst::SingletonInstId) {
return false;
}
}
// Attempt to match `param_inst` against `arg_id`. If the match succeeds,
// this should `continue` the outer loop. On `break`, we will try to desugar
// the parameter to continue looking for a match.
auto param_inst = context().insts().Get(param_id);
CARBON_KIND_SWITCH(param_inst) {
// Deducing a symbolic binding pattern from an argument deduces the
// binding as having that constant value. For example, deducing
// `(T:! type)` against `(i32)` deduces `T` to be `i32`. This only arises
// when initializing a generic parameter from an explicitly specified
// argument, and in this case, the argument is required to be a
// compile-time constant.
case CARBON_KIND(SemIR::SymbolicBindingPattern bind): {
auto& entity_name = context().entity_names().Get(bind.entity_name_id);
auto index = entity_name.bind_index;
if (!index.has_value()) {
break;
}
CARBON_CHECK(
index >= first_deduced_index_ &&
static_cast<size_t>(index.index) < result_arg_ids_.size(),
"Unexpected index {0} for symbolic binding pattern; "
"expected to be in range [{1}, {2})",
index.index, first_deduced_index_.index, result_arg_ids_.size());
CARBON_CHECK(!result_arg_ids_[index.index].has_value(),
"Deduced a value for parameter prior to its declaration");
auto arg_const_inst_id =
context().constant_values().GetConstantInstId(arg_id);
if (!arg_const_inst_id.has_value()) {
if (diagnose_) {
CARBON_DIAGNOSTIC(CompTimeArgumentNotConstant, Error,
"argument for generic parameter is not a "
"compile-time constant");
auto diag =
context().emitter().Build(loc_id_, CompTimeArgumentNotConstant);
NoteInitializingParam(param_id, diag);
diag.Emit();
}
return false;
}
result_arg_ids_[index.index] = arg_const_inst_id;
// This parameter index should not be deduced if it appears later.
non_deduced_indexes_[index.index - first_deduced_index_.index] = true;
continue;
}
// Deducing a symbolic binding appearing within an expression against a
// constant value deduces the binding as having that value. For example,
// deducing `[T:! type](x: T)` against `("foo")` deduces `T` as `String`.
case CARBON_KIND(SemIR::BindSymbolicName bind): {
auto& entity_name = context().entity_names().Get(bind.entity_name_id);
auto index = entity_name.bind_index;
if (!index.has_value() || index < first_deduced_index_ ||
non_deduced_indexes_[index.index - first_deduced_index_.index]) {
break;
}
CARBON_CHECK(static_cast<size_t>(index.index) < result_arg_ids_.size(),
"Deduced value for unexpected index {0}; expected to "
"deduce {1} arguments.",
index, result_arg_ids_.size());
auto arg_const_inst_id =
context().constant_values().GetConstantInstId(arg_id);
if (arg_const_inst_id.has_value()) {
if (result_arg_ids_[index.index].has_value() &&
result_arg_ids_[index.index] != arg_const_inst_id) {
if (diagnose_) {
// TODO: Include the two different deduced values.
CARBON_DIAGNOSTIC(DeductionInconsistent, Error,
"inconsistent deductions for value of generic "
"parameter `{0}`",
SemIR::NameId);
auto diag = context().emitter().Build(
loc_id_, DeductionInconsistent, entity_name.name_id);
NoteGenericHere(context(), generic_id_, diag);
diag.Emit();
}
return false;
}
result_arg_ids_[index.index] = arg_const_inst_id;
}
continue;
}
case CARBON_KIND(SemIR::ValueParamPattern pattern): {
Add(pattern.subpattern_id, arg_id, needs_substitution);
continue;
}
case SemIR::StructValue::Kind:
// TODO: Match field name order between param and arg.
break;
// TODO: Handle more cases.
default:
if (param_inst.kind().deduce_through()) {
// Various kinds of parameter should match an argument of the same
// form, if the operands all match.
auto arg_inst = context().insts().Get(arg_id);
if (arg_inst.kind() != param_inst.kind()) {
break;
}
auto [kind0, kind1] = param_inst.ArgKinds();
worklist_.AddInstArg(kind0, param_inst.arg0(), arg_inst.arg0(),
needs_substitution);
worklist_.AddInstArg(kind1, param_inst.arg1(), arg_inst.arg1(),
needs_substitution);
continue;
}
break;
}
// We didn't manage to deduce against the syntactic form of the parameter.
// Convert it to a canonical constant value and try deducing against that.
auto param_const_id = context().constant_values().Get(param_id);
if (!param_const_id.has_value() || !param_const_id.is_symbolic()) {
// It's not a symbolic constant. There's nothing here to deduce.
continue;
}
auto param_const_inst_id =
context().constant_values().GetInstId(param_const_id);
if (param_const_inst_id != param_id) {
Add(param_const_inst_id, arg_id, needs_substitution);
continue;
}
// If we've not yet substituted into the parameter, do so now and try again.
if (needs_substitution) {
param_const_id = SubstConstant(context(), param_const_id, substitutions_);
if (!param_const_id.has_value() || !param_const_id.is_symbolic()) {
continue;
}
Add(context().constant_values().GetInstId(param_const_id), arg_id,
/*needs_substitution=*/false);
}
}
return true;
}
auto DeductionContext::CheckDeductionIsComplete() -> bool {
// Check we deduced an argument value for every parameter, and convert each
// argument to match the final parameter type after substituting any deduced
// types it depends on.
for (auto&& [i, deduced_arg_id] :
llvm::enumerate(llvm::MutableArrayRef(result_arg_ids_)
.drop_front(first_deduced_index_.index))) {
auto binding_index = first_deduced_index_.index + i;
auto binding_id = context().inst_blocks().Get(
context().generics().Get(generic_id_).bindings_id)[binding_index];
if (!deduced_arg_id.has_value()) {
if (diagnose_) {
auto entity_name_id = context()
.insts()
.GetAs<SemIR::AnyBindName>(binding_id)
.entity_name_id;
CARBON_DIAGNOSTIC(DeductionIncomplete, Error,
"cannot deduce value for generic parameter `{0}`",
SemIR::NameId);
auto diag = context().emitter().Build(
loc_id_, DeductionIncomplete,
context().entity_names().Get(entity_name_id).name_id);
NoteGenericHere(context(), generic_id_, diag);
diag.Emit();
}
return false;
}
// If the binding is symbolic it can refer to other earlier bindings in the
// same generic, or from an enclosing specific. Substitute to replace those
// and get a non-symbolic type in order for us to know the final type that
// the argument needs to be converted to.
//
// Note that when typechecking a checked generic, the arguments can
// still be symbolic, so the substitution would also be symbolic. We are
// unable to get the final type for symbolic bindings until deducing with
// non-symbolic arguments.
//
// TODO: If arguments of different values, but that _convert to_ the same
// value, are deduced for the same symbolic binding, then we will fail
// typechecking in Deduce() with conflicting types via the
// `DeductionInconsistent` diagnostic. If we defer that check until after
// all conversions are done (after the code below) then we won't diagnose
// that incorrectly.
auto arg_type_id = context().insts().Get(deduced_arg_id).type_id();
auto binding_type_id = context().insts().Get(binding_id).type_id();
if (!arg_type_id.AsConstantId().is_symbolic() &&
binding_type_id.AsConstantId().is_symbolic()) {
auto param_type_const_id = SubstConstant(
context(), binding_type_id.AsConstantId(), substitutions_);
CARBON_CHECK(param_type_const_id.has_value());
CARBON_CHECK(!param_type_const_id.is_symbolic());
binding_type_id = context().GetTypeIdForTypeConstant(param_type_const_id);
// TODO: Suppress diagnostics here if `diagnose_` is false.
DiagnosticAnnotationScope annotate_diagnostics(
&context().emitter(),
[&](auto& builder) { NoteInitializingParam(binding_id, builder); });
auto converted_arg_id = ConvertToValueOfType(
context(), loc_id_, deduced_arg_id, binding_type_id);
// Replace the deduced arg with its value converted to the parameter
// type. The conversion of the argument type must produce a constant value
// to be used in deduction.
if (context().constant_values().Get(converted_arg_id).is_constant()) {
deduced_arg_id = converted_arg_id;
} else {
CARBON_DIAGNOSTIC(RuntimeConversionDuringCompTimeDeduction, Error,
"compile-time value requires runtime conversion, "
"constructing value of type {0}",
SemIR::TypeId);
auto diag = context().emitter().Build(
loc_id_, RuntimeConversionDuringCompTimeDeduction, binding_type_id);
NoteGenericHere(context(), generic_id_, diag);
diag.Emit();
deduced_arg_id = SemIR::ErrorInst::SingletonInstId;
}
}
substitutions_.push_back(
{.bind_id = SemIR::CompileTimeBindIndex(binding_index),
.replacement_id = context().constant_values().Get(deduced_arg_id)});
}
return true;
}
auto DeductionContext::MakeSpecific() -> SemIR::SpecificId {
// TODO: Convert the deduced values to the types of the bindings.
return Check::MakeSpecific(context(), loc_id_, generic_id_, result_arg_ids_);
}
auto DeduceGenericCallArguments(
Context& context, SemIR::LocId loc_id, SemIR::GenericId generic_id,
SemIR::SpecificId enclosing_specific_id, SemIR::InstId self_type_id,
[[maybe_unused]] SemIR::InstBlockId implicit_params_id,
SemIR::InstBlockId params_id, [[maybe_unused]] SemIR::InstId self_id,
llvm::ArrayRef<SemIR::InstId> arg_ids) -> SemIR::SpecificId {
DeductionContext deduction(context, loc_id, generic_id, enclosing_specific_id,
self_type_id, /*diagnose=*/true);
// Prepare to perform deduction of the explicit parameters against their
// arguments.
// TODO: Also perform deduction for type of self.
deduction.AddAll(params_id, arg_ids, /*needs_substitution=*/true);
if (!deduction.Deduce() || !deduction.CheckDeductionIsComplete()) {
return SemIR::SpecificId::None;
}
return deduction.MakeSpecific();
}
// Deduces the impl arguments to use in a use of a parameterized impl. Returns
// `None` if deduction fails.
auto DeduceImplArguments(Context& context, SemIR::LocId loc_id,
const SemIR::Impl& impl, SemIR::ConstantId self_id,
SemIR::ConstantId constraint_id) -> SemIR::SpecificId {
DeductionContext deduction(context, loc_id, impl.generic_id,
/*enclosing_specific_id=*/SemIR::SpecificId::None,
/*self_type_id=*/SemIR::InstId::None,
/*diagnose=*/false);
// Prepare to perform deduction of the type and interface.
deduction.Add(impl.self_id, context.constant_values().GetInstId(self_id),
/*needs_substitution=*/false);
deduction.Add(impl.constraint_id,
context.constant_values().GetInstId(constraint_id),
/*needs_substitution=*/false);
if (!deduction.Deduce() || !deduction.CheckDeductionIsComplete()) {
return SemIR::SpecificId::None;
}
return deduction.MakeSpecific();
}
} // namespace Carbon::Check