forked from carbon-language/carbon-lang
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial rough support for deducing generic arguments in a call to a g…
…eneric function. (carbon-language#4184)
- Loading branch information
Showing
13 changed files
with
1,324 additions
and
145 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,220 @@ | ||
// 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 "toolchain/base/kind_switch.h" | ||
#include "toolchain/check/context.h" | ||
#include "toolchain/check/generic.h" | ||
#include "toolchain/check/subst.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 list of (param, arg) deductions. These are added in reverse order so | ||
// they are popped in forward order. | ||
auto AddAll(llvm::ArrayRef<SemIR::InstId> params, | ||
llvm::ArrayRef<SemIR::InstId> 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::InstBlockId params, SemIR::InstBlockId args, | ||
bool needs_substitution) -> void { | ||
AddAll(context_.inst_blocks().Get(params), context_.inst_blocks().Get(args), | ||
needs_substitution); | ||
} | ||
|
||
// 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_; | ||
}; | ||
} // 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); | ||
} | ||
|
||
auto DeduceGenericCallArguments( | ||
Context& context, Parse::NodeId node_id, SemIR::GenericId generic_id, | ||
SemIR::SpecificId enclosing_specific_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 { | ||
DeductionWorklist worklist(context); | ||
|
||
llvm::SmallVector<SemIR::InstId> result_arg_ids; | ||
llvm::SmallVector<Substitution> substitutions; | ||
|
||
// Copy any outer generic arguments from the specified instance and prepare to | ||
// substitute them into the function declaration. | ||
if (enclosing_specific_id.is_valid()) { | ||
auto args = context.inst_blocks().Get( | ||
context.specifics().Get(enclosing_specific_id).args_id); | ||
result_arg_ids.assign(args.begin(), args.end()); | ||
|
||
// 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()); | ||
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)}); | ||
} | ||
} | ||
auto first_deduced_index = SemIR::CompileTimeBindIndex(result_arg_ids.size()); | ||
|
||
// Initialize the deduced arguments to Invalid. | ||
result_arg_ids.resize(context.inst_blocks() | ||
.Get(context.generics().Get(generic_id).bindings_id) | ||
.size(), | ||
SemIR::InstId::Invalid); | ||
|
||
// Prepare to perform deduction of the explicit parameters against their | ||
// arguments. | ||
// TODO: Also perform deduction for type of self. | ||
worklist.AddAll(params_id, arg_ids, /*needs_substitution=*/true); | ||
|
||
while (!worklist.Done()) { | ||
auto [param_id, arg_id, needs_substitution] = worklist.PopNext(); | ||
|
||
// If the parameter has a symbolic type, deduce against that. | ||
auto param_type_id = context.insts().Get(param_id).type_id(); | ||
if (param_type_id.AsConstantId().is_symbolic()) { | ||
worklist.Add( | ||
context.types().GetInstId(param_type_id), | ||
context.types().GetInstId(context.insts().Get(arg_id).type_id()), | ||
needs_substitution); | ||
} | ||
|
||
// If the parameter is a symbolic constant, deduce against it. | ||
auto param_const_id = context.constant_values().Get(param_id); | ||
if (!param_const_id.is_valid() || !param_const_id.is_symbolic()) { | ||
continue; | ||
} | ||
|
||
// If we've not yet substituted into the parameter, do so now. | ||
if (needs_substitution) { | ||
param_const_id = SubstConstant(context, param_const_id, substitutions); | ||
if (!param_const_id.is_valid() || !param_const_id.is_symbolic()) { | ||
continue; | ||
} | ||
needs_substitution = false; | ||
} | ||
|
||
CARBON_KIND_SWITCH(context.insts().Get(context.constant_values().GetInstId( | ||
param_const_id))) { | ||
// Deducing a symbolic binding from an argument with a constant value | ||
// deduces the binding as having that constant value. | ||
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.is_valid() && index >= first_deduced_index) { | ||
CARBON_CHECK(static_cast<size_t>(index.index) < result_arg_ids.size()) | ||
<< "Deduced value for unexpected index " << index | ||
<< "; expected to deduce " << result_arg_ids.size() | ||
<< " arguments."; | ||
auto arg_const_inst_id = | ||
context.constant_values().GetConstantInstId(arg_id); | ||
if (arg_const_inst_id.is_valid()) { | ||
if (result_arg_ids[index.index].is_valid() && | ||
result_arg_ids[index.index] != arg_const_inst_id) { | ||
// 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( | ||
node_id, DeductionInconsistent, entity_name.name_id); | ||
NoteGenericHere(context, generic_id, diag); | ||
diag.Emit(); | ||
return SemIR::SpecificId::Invalid; | ||
} | ||
result_arg_ids[index.index] = arg_const_inst_id; | ||
} | ||
} | ||
break; | ||
} | ||
|
||
// TODO: Handle more cases. | ||
|
||
default: | ||
break; | ||
} | ||
} | ||
|
||
// Check we deduced an argument value for every parameter. | ||
for (auto [i, deduced_arg_id] : | ||
llvm::enumerate(llvm::ArrayRef(result_arg_ids) | ||
.drop_front(first_deduced_index.index))) { | ||
if (!deduced_arg_id.is_valid()) { | ||
auto binding_index = first_deduced_index.index + i; | ||
auto binding_id = context.inst_blocks().Get( | ||
context.generics().Get(generic_id).bindings_id)[binding_index]; | ||
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( | ||
node_id, DeductionIncomplete, | ||
context.entity_names().Get(entity_name_id).name_id); | ||
NoteGenericHere(context, generic_id, diag); | ||
diag.Emit(); | ||
return SemIR::SpecificId::Invalid; | ||
} | ||
} | ||
|
||
// TODO: Convert the deduced values to the types of the bindings. | ||
|
||
return MakeSpecific(context, generic_id, | ||
context.inst_blocks().AddCanonical(result_arg_ids)); | ||
} | ||
|
||
} // namespace Carbon::Check |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
// 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_DEDUCE_H_ | ||
#define CARBON_TOOLCHAIN_CHECK_DEDUCE_H_ | ||
|
||
#include "toolchain/check/context.h" | ||
#include "toolchain/sem_ir/ids.h" | ||
|
||
namespace Carbon::Check { | ||
|
||
// Deduces the generic arguments to use in a call to a generic. | ||
auto DeduceGenericCallArguments(Context& context, Parse::NodeId node_id, | ||
SemIR::GenericId generic_id, | ||
SemIR::SpecificId enclosing_specific_id, | ||
SemIR::InstBlockId implicit_params_id, | ||
SemIR::InstBlockId params_id, | ||
SemIR::InstId self_id, | ||
llvm::ArrayRef<SemIR::InstId> arg_ids) | ||
-> SemIR::SpecificId; | ||
|
||
} // namespace Carbon::Check | ||
|
||
#endif // CARBON_TOOLCHAIN_CHECK_DEDUCE_H_ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.