Stringify support for call instructions. (#7827)

Reconstruct the call syntax from the callee's explicit parameter
patterns, the callee specific, and the call arguments.

Assisted-by: Claude via Antigravity.
This commit is contained in:
Richard Smith
2026-09-28 22:23:32 +00:00
committed by GitHub
parent c99736bcb9
commit c1ca91b67a
3 changed files with 251 additions and 1 deletions
+173
View File
@@ -335,6 +335,179 @@ class Stringifier {
step_stack_->PushEntityNameId(inst.entity_name_id);
}
// Pushes the argument list of a call, including the enclosing parentheses.
//
// The arguments of a `Call` are the arguments of the SemIR calling
// convention: compile-time arguments are absent, because they're instead
// found in the callee's specific, and the remaining arguments are flattened
// by pattern matching. In order to print the call as it was written, we walk
// the parameter patterns of the callee and pick out the argument
// corresponding to each leaf pattern.
auto PushCallArgs(llvm::ArrayRef<InstId> param_patterns,
llvm::ArrayRef<InstId> args,
llvm::ArrayRef<InstId> specific_args) -> void {
// The pieces of the argument list that we've not printed yet, in print
// order. We process these from the back, so that we walk the patterns from
// right to left, which is the order in which the step stack wants to be
// given them, and which lets us consume `args` from the back.
llvm::SmallVector<std::variant<InstId, llvm::StringRef>> worklist;
worklist.push_back(llvm::StringRef("("));
llvm::ListSeparator sep;
for (auto param_id : param_patterns) {
worklist.push_back(llvm::StringRef(sep));
worklist.push_back(param_id);
}
worklist.push_back(llvm::StringRef(")"));
while (!worklist.empty()) {
auto next = worklist.pop_back_val();
if (auto* string = std::get_if<llvm::StringRef>(&next)) {
step_stack_->PushString(*string);
continue;
}
auto pattern_id = std::get<InstId>(next);
CARBON_KIND_SWITCH(sem_ir_->insts().Get(pattern_id)) {
case CARBON_KIND(TuplePattern tuple): {
auto elements = sem_ir_->inst_blocks().Get(tuple.elements_id);
worklist.push_back(llvm::StringRef("("));
llvm::ListSeparator element_sep;
for (auto element_id : elements) {
worklist.push_back(llvm::StringRef(element_sep));
worklist.push_back(element_id);
}
// A tuple of one element has a comma to disambiguate from a
// parenthesized pattern.
worklist.push_back(
llvm::StringRef(elements.size() == 1 ? ",)" : ")"));
break;
}
case CARBON_KIND_ANY(AnyVarPattern, var_pattern): {
worklist.push_back(var_pattern.subpattern_id);
break;
}
case CARBON_KIND(DefaultValuePattern default_value): {
worklist.push_back(default_value.subpattern_id);
break;
}
case CARBON_KIND_ANY(AnyBindingPattern, binding): {
if (binding.subpattern_id.has_value()) {
worklist.push_back(binding.subpattern_id);
break;
}
// A compile-time binding's argument is an argument of the specific.
auto bind_index =
sem_ir_->entity_names().Get(binding.entity_name_id).bind_index();
if (bind_index.has_value() &&
static_cast<size_t>(bind_index.index) < specific_args.size()) {
step_stack_->PushInstId(specific_args[bind_index.index]);
} else {
// We don't know the argument, so name the parameter instead.
step_stack_->PushEntityNameId(binding.entity_name_id);
}
break;
}
case CARBON_KIND_ANY(AnyLeafParamPattern, _): {
// A runtime parameter's argument is the next argument of the call,
// taken from the back because we're walking right to left.
if (args.empty()) {
step_stack_->PushString("<missing argument>");
} else {
step_stack_->PushInstId(args.back());
args = args.drop_back();
}
break;
}
default: {
// We don't know how to find the argument for this pattern, so print
// the pattern instead.
step_stack_->PushInstId(pattern_id);
break;
}
}
}
}
auto StringifyInst(InstId inst_id, Call inst) -> void {
// If the call has a different constant value, for example because it was
// evaluated at compile time, print that instead.
auto const_inst_id = sem_ir_->constant_values().GetConstantInstId(inst_id);
if (const_inst_id.has_value() && const_inst_id != inst_id) {
step_stack_->PushInstId(const_inst_id);
return;
}
auto args = sem_ir_->inst_blocks().Get(inst.args_id);
auto callee = GetCallee(*sem_ir_, inst.callee_id);
auto* callee_fn = std::get_if<CalleeFunction>(&callee);
if (!callee_fn) {
// We don't know the signature of the callee, so print the arguments of
// the `Call` directly.
step_stack_->PushString(")");
llvm::ListSeparator sep;
for (auto arg : llvm::reverse(args)) {
step_stack_->Push(arg, &sep);
}
step_stack_->Push("(", inst.callee_id);
return;
}
const auto& function = sem_ir_->functions().Get(callee_fn->function_id);
auto specific_id = callee_fn->resolved_specific_id.has_value()
? callee_fn->resolved_specific_id
: callee_fn->enclosing_specific_id;
llvm::ArrayRef<InstId> param_patterns;
if (function.param_patterns_id.has_value()) {
param_patterns = sem_ir_->inst_blocks().Get(function.param_patterns_id);
}
llvm::ArrayRef<InstId> specific_args;
if (specific_id.has_value()) {
specific_args = sem_ir_->inst_blocks().Get(
sem_ir_->specifics().Get(specific_id).args_id);
}
// Only the explicit parameters are written as arguments in the call.
const auto& param_ranges = function.call_param_ranges;
auto args_begin =
std::min<size_t>(param_ranges.explicit_begin().index, args.size());
auto args_end =
std::min<size_t>(param_ranges.explicit_end().index, args.size());
args = args.slice(args_begin, args_end - args_begin);
// In a method call, `self` is the first explicit parameter, but is written
// before the name of the function rather than in the argument list.
auto self_id = InstId::None;
if (callee_fn->self_id.has_value() && function.self_param_id.has_value() &&
!param_patterns.empty() && !args.empty()) {
self_id = args.front();
args = args.drop_front();
param_patterns = param_patterns.drop_front();
}
PushCallArgs(param_patterns, args, specific_args);
// Print the name of the callee. Note that we avoid stringifying the callee
// instruction itself when it names a function, because that would print
// the function's parameter list, and we're printing the call's arguments
// instead.
if (auto specific_impl_fn =
sem_ir_->insts().TryGetAs<SpecificImplFunction>(inst.callee_id)) {
// The callee of a `specific_impl_function` is an `impl_witness_access`,
// which names both the interface function and the `Self` type.
step_stack_->PushInstId(specific_impl_fn->callee_id);
} else {
step_stack_->PushQualifiedName(function.parent_scope_id,
function.name_id);
}
if (self_id.has_value()) {
// TODO: Omit the parentheses when they're not needed.
step_stack_->Push("(", self_id, ").");
}
}
auto StringifyInst(InstId /*inst_id*/, ClassType inst) -> void {
const auto& class_info = sem_ir_->classes().Get(inst.class_id);
if (auto type_info = RecognizedTypeInfo::ForType(*sem_ir_, inst);