From c1ca91b67ab01236df9371527e2f443e8dbd6107 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Mon, 28 Sep 2026 22:23:32 +0000 Subject: [PATCH] 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. --- toolchain/check/testdata/eval/symbolic.carbon | 77 ++++++++ .../check/testdata/facet/period_self.carbon | 2 +- toolchain/sem_ir/stringify.cpp | 173 ++++++++++++++++++ 3 files changed, 251 insertions(+), 1 deletion(-) diff --git a/toolchain/check/testdata/eval/symbolic.carbon b/toolchain/check/testdata/eval/symbolic.carbon index 8e2fed938f64..284b77718348 100644 --- a/toolchain/check/testdata/eval/symbolic.carbon +++ b/toolchain/check/testdata/eval/symbolic.carbon @@ -68,6 +68,83 @@ fn F() { E({}); } +// --- fail_print_call_in_diagnostic.carbon +library "[[@TEST_NAME]]"; + +eval fn F(generic B: bool) -> type { + if (B) { return (); } + return bool; +} + +eval fn G(generic B: bool, (a: bool, b: bool)) -> type { + if (B and a and b) { return (); } + return bool; +} + +// A redeclaration with a mismatched parameter type prints the symbolic call as +// the type of the parameter. The argument for the generic parameter is found +// in the specific for the callee, not in the arguments of the SemIR `call`. +fn UseF(generic B: bool, x: F(B)); +// CHECK:STDERR: fail_print_call_in_diagnostic.carbon:[[@LINE+7]]:26: error: type `` of parameter 2 in redeclaration differs from previous parameter type `` [RedeclParamDiffersType] +// CHECK:STDERR: fn UseF(generic B: bool, x: bool); +// CHECK:STDERR: ^~~~~~~ +// CHECK:STDERR: fail_print_call_in_diagnostic.carbon:[[@LINE-4]]:26: note: previous declaration's corresponding parameter here [RedeclParamPrevious] +// CHECK:STDERR: fn UseF(generic B: bool, x: F(B)); +// CHECK:STDERR: ^~~~~~~ +// CHECK:STDERR: +fn UseF(generic B: bool, x: bool); + +// The arguments of the SemIR `call` are flattened by the nested tuple pattern +// in the signature of `G`, and are regrouped here to match the source. +fn UseG(generic B: bool, x: G(B, (true, false))); +// CHECK:STDERR: fail_print_call_in_diagnostic.carbon:[[@LINE+7]]:26: error: type `` of parameter 2 in redeclaration differs from previous parameter type `` [RedeclParamDiffersType] +// CHECK:STDERR: fn UseG(generic B: bool, x: bool); +// CHECK:STDERR: ^~~~~~~ +// CHECK:STDERR: fail_print_call_in_diagnostic.carbon:[[@LINE-4]]:26: note: previous declaration's corresponding parameter here [RedeclParamPrevious] +// CHECK:STDERR: fn UseG(generic B: bool, x: G(B, (true, false))); +// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~ +// CHECK:STDERR: +fn UseG(generic B: bool, x: bool); + +// A one-element tuple pattern needs a trailing comma. +eval fn H(generic B: bool, (a: bool,)) -> type { + if (B and a) { return (); } + return bool; +} + +fn UseH(generic B: bool, x: H(B, (true,))); +// CHECK:STDERR: fail_print_call_in_diagnostic.carbon:[[@LINE+7]]:26: error: type `` of parameter 2 in redeclaration differs from previous parameter type `` [RedeclParamDiffersType] +// CHECK:STDERR: fn UseH(generic B: bool, x: bool); +// CHECK:STDERR: ^~~~~~~ +// CHECK:STDERR: fail_print_call_in_diagnostic.carbon:[[@LINE-4]]:26: note: previous declaration's corresponding parameter here [RedeclParamPrevious] +// CHECK:STDERR: fn UseH(generic B: bool, x: H(B, (true,))); +// CHECK:STDERR: ^~~~~~~~~~~~~~~~ +// CHECK:STDERR: +fn UseH(generic B: bool, x: bool); + +// The `self` argument is the first argument of the SemIR `call`, but is printed +// before the name of the method, as it was written. +class C { + // CHECK:STDERR: fail_print_call_in_diagnostic.carbon:[[@LINE+4]]:13: warning: binding `self` unused [UnusedBinding] + // CHECK:STDERR: eval fn M(self: Self, b: bool) -> type { + // CHECK:STDERR: ^~~~~~~~~~ + // CHECK:STDERR: + eval fn M(self: Self, b: bool) -> type { + if (b) { return (); } + return bool; + } +} + +fn UseM(generic c: C, x: c.M(true)); +// CHECK:STDERR: fail_print_call_in_diagnostic.carbon:[[@LINE+7]]:23: error: type `` of parameter 2 in redeclaration differs from previous parameter type `` [RedeclParamDiffersType] +// CHECK:STDERR: fn UseM(generic c: C, x: bool); +// CHECK:STDERR: ^~~~~~~ +// CHECK:STDERR: fail_print_call_in_diagnostic.carbon:[[@LINE-4]]:23: note: previous declaration's corresponding parameter here [RedeclParamPrevious] +// CHECK:STDERR: fn UseM(generic c: C, x: c.M(true)); +// CHECK:STDERR: ^~~~~~~~~~~~ +// CHECK:STDERR: +fn UseM(generic c: C, x: bool); + // CHECK:STDOUT: --- dependent_symbolic_instruction.carbon // CHECK:STDOUT: // CHECK:STDOUT: constants { diff --git a/toolchain/check/testdata/facet/period_self.carbon b/toolchain/check/testdata/facet/period_self.carbon index 944d9f19c67c..aafd4e2b0f34 100644 --- a/toolchain/check/testdata/facet/period_self.carbon +++ b/toolchain/check/testdata/facet/period_self.carbon @@ -816,7 +816,7 @@ interface Y(T: type) { // TODO: We crash if this impl is not generic, as the call to `.YF()` produces a // symbolic-dependent instruction, but there's no generic eval block for it. -// CHECK:STDERR: fail_todo_period_self_in_generic_argument.carbon:[[@LINE+4]]:60: error: cannot convert type `` that implements `Z where .(Z.Z1) = ()` into type implementing `Z where .(Z.Z1) = ()` [ConversionFailureFacetToFacet] +// CHECK:STDERR: fail_todo_period_self_in_generic_argument.carbon:[[@LINE+4]]:60: error: cannot convert type `.(Y(Z where .(Z.Z1) = ()).YF)()` that implements `Z where .(Z.Z1) = ()` into type implementing `Z where .(Z.Z1) = ()` [ConversionFailureFacetToFacet] // CHECK:STDERR: impl forall [T: type] T as Y(Z where .Z1 = ()) where .Y1 = .YF() {} // CHECK:STDERR: ^~~~~ // CHECK:STDERR: diff --git a/toolchain/sem_ir/stringify.cpp b/toolchain/sem_ir/stringify.cpp index 4e21e1f60dee..2927f66187b0 100644 --- a/toolchain/sem_ir/stringify.cpp +++ b/toolchain/sem_ir/stringify.cpp @@ -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 param_patterns, + llvm::ArrayRef args, + llvm::ArrayRef 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> 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(&next)) { + step_stack_->PushString(*string); + continue; + } + + auto pattern_id = std::get(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(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(""); + } 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(&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 param_patterns; + if (function.param_patterns_id.has_value()) { + param_patterns = sem_ir_->inst_blocks().Get(function.param_patterns_id); + } + + llvm::ArrayRef 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(param_ranges.explicit_begin().index, args.size()); + auto args_end = + std::min(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(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);