Files
carbon-lang/toolchain/check/function.cpp
T
Lucile Rose Nihlen ca9e985fa8 Reconcile function default values between decl and def (#7665)
Updates the pattern matching code to support unspecified default values.
Adds logic to decl and def merge code to diagnose mismatches in defaults
if specified in both places, or if let entirely unspecified.

Per https://github.com/carbon-language/carbon-lang/pull/7521.
2026-09-08 20:41:58 +00:00

594 lines
25 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/function.h"
#include "common/find.h"
#include "toolchain/base/kind_switch.h"
#include "toolchain/check/action.h"
#include "toolchain/check/convert.h"
#include "toolchain/check/eval.h"
#include "toolchain/check/generic.h"
#include "toolchain/check/inst.h"
#include "toolchain/check/merge.h"
#include "toolchain/check/pattern.h"
#include "toolchain/check/pattern_match.h"
#include "toolchain/check/scope_stack.h"
#include "toolchain/check/type.h"
#include "toolchain/check/type_completion.h"
#include "toolchain/diagnostics/format_providers.h"
#include "toolchain/sem_ir/builtin_function_kind.h"
#include "toolchain/sem_ir/ids.h"
#include "toolchain/sem_ir/pattern.h"
namespace Carbon::Check {
auto FindSelfPattern(Context& context,
SemIR::InstBlockId implicit_param_patterns_id,
SemIR::InstBlockId param_patterns_id) -> SemIR::InstId {
auto is_self_pattern = [&](auto param_id) {
return SemIR::IsSelfPattern(context.sem_ir(), param_id);
};
// `self` is the first explicit parameter. We also look in the implicit
// parameter list for error recovery: declaring `self` there is diagnosed (see
// `SelfInImplicitParamList`), but we still treat it as `self` afterwards.
auto param_patterns = context.inst_blocks().GetOrEmpty(param_patterns_id);
if (auto self_id = FindIfOrNone(param_patterns, is_self_pattern);
self_id.has_value()) {
return self_id;
}
auto implicit_param_patterns =
context.inst_blocks().GetOrEmpty(implicit_param_patterns_id);
return FindIfOrNone(implicit_param_patterns, is_self_pattern);
}
auto AddReturnPattern(Context& context, SemIR::LocId loc_id,
Context::FormExpr form_expr) -> SemIR::InstId {
auto result_type_id = GetPatternType(context, form_expr.type_component_id);
auto result_type_inst_id = context.types().GetTypeInstId(result_type_id);
auto result_id = HandleAction<SemIR::OutFormParamPatternAction>(
context, loc_id, result_type_inst_id,
{.type_id = SemIR::InstType::TypeId, .form_id = form_expr.form_inst_id});
return AddInst<SemIR::ReturnSlotPattern>(
context, loc_id,
{.type_id = result_type_id,
.subpattern_id = result_id,
.type_inst_id = form_expr.type_component_inst_id});
}
auto IsValidBuiltinDeclaration(Context& context,
const SemIR::Function& function,
SemIR::BuiltinFunctionKind builtin_kind)
-> bool {
if (!function.call_params_id.has_value()) {
// For now, we have no builtins that support positional parameters.
return false;
}
// Find the list of call parameters other than the implicit return slots.
auto call_params =
context.inst_blocks()
.Get(function.call_params_id)
.take_front(function.call_param_ranges.explicit_end().index);
// Get the return type. This is `()` if none was specified.
auto return_type_id = function.GetDeclaredReturnType(context.sem_ir());
if (!return_type_id.has_value()) {
return_type_id = GetTupleType(context, {});
}
return builtin_kind.IsValidType(context.sem_ir(), call_params,
return_type_id);
}
namespace {
// Function signature fields for `MakeFunctionSignature`.
struct FunctionSignatureInsts {
SemIR::InstBlockId decl_block_id = SemIR::InstBlockId::None;
SemIR::InstBlockId pattern_block_id = SemIR::InstBlockId::None;
SemIR::InstBlockId implicit_param_patterns_id = SemIR::InstBlockId::None;
SemIR::InstBlockId param_patterns_id = SemIR::InstBlockId::None;
SemIR::InstBlockId call_param_patterns_id = SemIR::InstBlockId::None;
SemIR::InstBlockId call_params_id = SemIR::InstBlockId::None;
SemIR::InstBlockId call_param_default_values_id = SemIR::InstBlockId::None;
SemIR::Function::CallParamIndexRanges call_param_ranges =
SemIR::Function::CallParamIndexRanges::Empty;
SemIR::TypeInstId return_type_inst_id = SemIR::TypeInstId::None;
SemIR::InstId return_form_inst_id = SemIR::InstId::None;
SemIR::InstId return_pattern_id = SemIR::InstId::None;
SemIR::InstId self_param_id = SemIR::InstId::None;
};
} // namespace
// Handles construction of the signature's parameter and return types.
static auto MakeFunctionSignature(Context& context, SemIR::LocId loc_id,
const FunctionDeclArgs& args)
-> FunctionSignatureInsts {
FunctionSignatureInsts insts;
StartFunctionSignature(context);
// Build and add the explicit parameters, with a leading `self` parameter if
// one is needed. `self` is the first explicit parameter (proposal #7016),
// matching the convention used by user-written and prelude signatures.
// Keeping the placement consistent matters in particular for the
// `Destroy.Op` / `Copy.Op` functions backing custom witnesses: a mismatch
// would force a signature-adapting thunk for every witness.
context.full_pattern_stack().StartExplicitParamList();
if (!args.self_type_id.has_value() && args.param_type_ids.empty()) {
insts.param_patterns_id = SemIR::InstBlockId::Empty;
} else {
llvm::SmallVector<SemIR::InstId> param_patterns;
if (args.self_type_id.has_value()) {
auto self_type_region_id = MakeEmptyRegion(
context, context.types().GetTypeInstId(args.self_type_id));
insts.self_param_id = AddParamPattern(
context, loc_id, SemIR::NameId::SelfValue, self_type_region_id,
args.self_type_id, args.self_kind);
param_patterns.push_back(insts.self_param_id);
}
for (auto [param_type_id, param_kind] :
llvm::zip_equal(args.param_type_ids, args.param_kinds)) {
auto param_type_region_id = MakeEmptyRegion(
context, context.types().GetTypeInstId(param_type_id));
param_patterns.push_back(
AddParamPattern(context, loc_id, SemIR::NameId::Underscore,
param_type_region_id, param_type_id, param_kind));
}
insts.param_patterns_id = context.inst_blocks().Add(param_patterns);
}
context.full_pattern_stack().EndExplicitParamList();
if (args.return_form.form_inst_id.has_value()) {
insts.return_type_inst_id = args.return_form.type_component_inst_id;
insts.return_form_inst_id = args.return_form.form_inst_id;
insts.return_pattern_id =
AddReturnPattern(context, loc_id, args.return_form);
}
auto match_results =
CalleePatternMatch(context, insts.implicit_param_patterns_id,
insts.param_patterns_id, insts.return_pattern_id);
insts.call_param_patterns_id = match_results.call_param_patterns_id;
insts.call_params_id = match_results.call_params_id;
insts.call_param_patterns_id = match_results.call_param_patterns_id;
insts.call_param_ranges = match_results.param_ranges;
auto [pattern_block_id, decl_block_id] =
FinishFunctionSignature(context, /*check_unused=*/false);
insts.pattern_block_id = pattern_block_id;
insts.decl_block_id = decl_block_id;
return insts;
}
auto MakeGeneratedFunctionDecl(Context& context, SemIR::LocId loc_id,
const FunctionDeclArgs& args)
-> std::pair<SemIR::InstId, SemIR::FunctionId> {
auto insts = MakeFunctionSignature(context, loc_id, args);
// Add the function declaration.
auto [decl_id, function_id] = MakeFunctionDecl(
context, loc_id, insts.decl_block_id, /*build_generic=*/false,
/*is_definition=*/true,
SemIR::Function{
{
.name_id = args.name_id,
.parent_scope_id = args.parent_scope_id,
.generic_id = SemIR::GenericId::None,
.first_param_node_id = Parse::NodeId::None,
.last_param_node_id = Parse::NodeId::None,
.pattern_block_id = insts.pattern_block_id,
.implicit_param_patterns_id = insts.implicit_param_patterns_id,
.param_patterns_id = insts.param_patterns_id,
.is_extern = false,
.extern_library_id = SemIR::LibraryNameId::None,
.non_owning_decl_id = SemIR::InstId::None,
// Set by `MakeFunctionDecl`.
.first_owning_decl_id = SemIR::InstId::None,
},
{
.call_param_patterns_id = insts.call_param_patterns_id,
.call_params_id = insts.call_params_id,
.call_param_default_values_id =
insts.call_param_default_values_id,
.call_param_ranges = insts.call_param_ranges,
.return_type_inst_id = insts.return_type_inst_id,
.return_form_inst_id = insts.return_form_inst_id,
.return_pattern_id = insts.return_pattern_id,
.self_param_id = insts.self_param_id,
}});
context.generated().push_back(decl_id);
return {decl_id, function_id};
}
auto CheckFunctionReturnTypeMatches(Context& context,
const SemIR::Function& new_function,
const SemIR::Function& prev_function,
SemIR::SpecificId prev_specific_id,
bool diagnose) -> bool {
// TODO: Pass a specific ID for `prev_function` instead of substitutions and
// use it here.
auto new_return_type_id =
new_function.GetDeclaredReturnType(context.sem_ir());
auto prev_return_type_id =
prev_function.GetDeclaredReturnType(context.sem_ir(), prev_specific_id);
if (new_return_type_id == SemIR::ErrorInst::TypeId ||
prev_return_type_id == SemIR::ErrorInst::TypeId) {
return false;
}
if (!context.types().AreEqualAcrossDeclarations(new_return_type_id,
prev_return_type_id)) {
if (new_function.name_id == SemIR::NameId::CppOperator &&
!prev_return_type_id.has_value()) {
return true;
}
if (!diagnose) {
return false;
}
CARBON_DIAGNOSTIC(
FunctionRedeclReturnTypeDiffers, Error,
"function redeclaration differs because return type is {0}",
SemIR::TypeId);
CARBON_DIAGNOSTIC(
FunctionRedeclReturnTypeDiffersNoReturn, Error,
"function redeclaration differs because no return type is provided");
auto diag =
new_return_type_id.has_value()
? context.emitter().Build(new_function.latest_decl_id(),
FunctionRedeclReturnTypeDiffers,
new_return_type_id)
: context.emitter().Build(new_function.latest_decl_id(),
FunctionRedeclReturnTypeDiffersNoReturn);
if (prev_return_type_id.has_value()) {
CARBON_DIAGNOSTIC(FunctionRedeclReturnTypePrevious, Note,
"previously declared with return type {0}",
SemIR::TypeId);
diag.Note(prev_function.latest_decl_id(),
FunctionRedeclReturnTypePrevious, prev_return_type_id);
} else {
CARBON_DIAGNOSTIC(FunctionRedeclReturnTypePreviousNoReturn, Note,
"previously declared with no return type");
diag.Note(prev_function.latest_decl_id(),
FunctionRedeclReturnTypePreviousNoReturn);
}
diag.Emit();
return false;
}
return true;
}
// Checks that a function declaration's evaluation mode matches the previous
// declaration's evaluation mode. Returns `false` and optionally produces a
// diagnostic on mismatch.
static auto CheckFunctionEvaluationModeMatches(
Context& context, const SemIR::Function& new_function,
const SemIR::Function& prev_function, bool diagnose) -> bool {
if (prev_function.evaluation_mode == new_function.evaluation_mode) {
return true;
}
if (!diagnose) {
return false;
}
auto eval_mode_index = [](SemIR::Function::EvaluationMode mode) {
switch (mode) {
case SemIR::Function::EvaluationMode::None:
return 0;
case SemIR::Function::EvaluationMode::Eval:
return 1;
case SemIR::Function::EvaluationMode::MustEval:
return 2;
}
};
auto prev_eval_mode_index = eval_mode_index(prev_function.evaluation_mode);
auto new_eval_mode_index = eval_mode_index(new_function.evaluation_mode);
CARBON_DIAGNOSTIC(
FunctionRedeclEvaluationModeDiffers, Error,
"function redeclaration differs because new function is "
"{0:=-1:not `eval`|=-2:not `musteval`|=1:`eval`|=2:`musteval`}",
Diagnostics::IntAsSelect);
CARBON_DIAGNOSTIC(FunctionRedeclEvaluationModePrevious, Note,
"previously {0:<0:not |:}declared as "
"{0:=-1:`eval`|=-2:`musteval`|=1:`eval`|=2:`musteval`}",
Diagnostics::IntAsSelect);
context.emitter()
.Build(new_function.latest_decl_id(), FunctionRedeclEvaluationModeDiffers,
new_eval_mode_index ? new_eval_mode_index : -prev_eval_mode_index)
.Note(prev_function.latest_decl_id(),
FunctionRedeclEvaluationModePrevious,
prev_eval_mode_index ? prev_eval_mode_index : -new_eval_mode_index)
.Emit();
return false;
}
// Given a parameter patterns block, extracts the locations of all
// `SemIR::DefaultValuePattern` instructions and returns them in an array.
static auto ExtractDefaultValueLocations(Context& context,
SemIR::InstBlockId param_patterns_id)
-> llvm::SmallVector<SemIR::LocId> {
llvm::SmallVector<SemIR::LocId> locations;
for (auto inst_id : context.inst_blocks().GetOrEmpty(param_patterns_id)) {
if (context.insts().Is<SemIR::DefaultValuePattern>(inst_id)) {
locations.push_back(SemIR::LocId(inst_id));
}
}
return locations;
}
// Checks every parameter in `prev_function` and `new_function`, that if they
// both specify a default value those values are identical, or that at most
// one has an unspecified default value. If `diagnose` is true, issues
// diagnostics where either condition is violated. Returns true if every
// parameter met both criteria.
static auto CheckDefaultValueConsistency(Context& context,
const SemIR::Function& new_function,
const SemIR::Function& prev_function,
bool diagnose) -> bool {
// Both functions must either have defaults or not.
CARBON_CHECK(prev_function.call_param_default_values_id.has_value() ==
new_function.call_param_default_values_id.has_value());
if (!prev_function.call_param_default_values_id.has_value()) {
return true;
}
auto prev_value_inst_ids =
context.inst_blocks().Get(prev_function.call_param_default_values_id);
auto new_value_inst_ids =
context.inst_blocks().Get(new_function.call_param_default_values_id);
CARBON_CHECK(prev_value_inst_ids.size() == new_value_inst_ids.size());
llvm::SmallVector<size_t> indices_without_values;
llvm::SmallVector<size_t> indices_with_different_values;
for (size_t i = 0; i < prev_value_inst_ids.size(); ++i) {
bool prev_value_specified =
!context.insts().Is<SemIR::UnspecifiedValue>(prev_value_inst_ids[i]);
bool new_value_specified =
!context.insts().Is<SemIR::UnspecifiedValue>(new_value_inst_ids[i]);
if (!prev_value_specified && !new_value_specified) {
indices_without_values.push_back(i);
} else if (prev_value_specified && new_value_specified) {
auto prev_constant_id = TryEvalInst(context, prev_value_inst_ids[i]);
CARBON_CHECK(prev_constant_id != SemIR::ConstantId::NotConstant);
auto new_constant_id = TryEvalInst(context, new_value_inst_ids[i]);
CARBON_CHECK(new_constant_id != SemIR::ConstantId::NotConstant);
if (prev_constant_id != new_constant_id) {
indices_with_different_values.push_back(i);
}
}
}
bool check_ok =
indices_without_values.empty() && indices_with_different_values.empty();
if (check_ok || !diagnose) {
return check_ok;
}
// TODO: for imported functions we don't seem to have the previous parameter
// pattern block, so we can't add their locations to the diagnostic.
auto prev_param_locations =
ExtractDefaultValueLocations(context, prev_function.param_patterns_id);
auto new_param_locations =
ExtractDefaultValueLocations(context, new_function.param_patterns_id);
for (auto index : indices_without_values) {
CARBON_DIAGNOSTIC(PatternDefaultValueNeverSpecified, Error,
"no value for default number {0} is ever specified.",
size_t);
CARBON_DIAGNOSTIC(PatternDefaultValueNeverSpecifiedNote, Note,
"previous declaration here.");
auto builder = context.emitter().Build(
new_param_locations[index], PatternDefaultValueNeverSpecified, index);
if (index < prev_param_locations.size()) {
builder.Note(prev_param_locations[index],
PatternDefaultValueNeverSpecifiedNote);
}
builder.Emit();
}
for (auto index : indices_with_different_values) {
CARBON_DIAGNOSTIC(PatternDefaultValueDiffers, Error,
"default value differs from the previous declaration.");
context.emitter().Emit(new_param_locations[index],
PatternDefaultValueDiffers);
}
return false;
}
auto CheckFunctionTypeMatches(Context& context,
const SemIR::Function& new_function,
const SemIR::Function& prev_function,
SemIR::SpecificId prev_specific_id,
bool check_syntax, bool diagnose) -> bool {
if (!CheckRedeclParamsMatch(context, DeclParams(new_function),
DeclParams(prev_function), prev_specific_id,
diagnose, check_syntax)) {
return false;
}
if (!CheckFunctionReturnTypeMatches(context, new_function, prev_function,
prev_specific_id, diagnose)) {
return false;
}
if (!CheckFunctionEvaluationModeMatches(context, new_function, prev_function,
diagnose)) {
return false;
}
if (!CheckDefaultValueConsistency(context, new_function, prev_function,
diagnose)) {
return false;
}
return true;
}
auto CheckFunctionReturnPatternType(Context& context, SemIR::LocId loc_id,
SemIR::InstId return_pattern_id,
SemIR::SpecificId specific_id)
-> SemIR::TypeId {
auto arg_type_id = SemIR::ExtractScrutineeType(
context.sem_ir(), SemIR::GetTypeOfInstInSpecific(
context.sem_ir(), specific_id, return_pattern_id));
auto init_repr = SemIR::InitRepr::ForType(context.sem_ir(), arg_type_id);
if (!init_repr.is_valid()) {
// TODO: Consider suppressing the diagnostics if we've already diagnosed a
// definition or call to this function.
if (!RequireConcreteType(
context, arg_type_id, SemIR::LocId(return_pattern_id),
[&](auto& builder) {
CARBON_DIAGNOSTIC(IncompleteTypeInFunctionReturnType, Context,
"function returns incomplete type {0}",
SemIR::TypeId);
builder.Context(loc_id, IncompleteTypeInFunctionReturnType,
arg_type_id);
},
[&](auto& builder) {
CARBON_DIAGNOSTIC(AbstractTypeInFunctionReturnType, Context,
"function returns abstract type {0}",
SemIR::TypeId);
builder.Context(loc_id, AbstractTypeInFunctionReturnType,
arg_type_id);
})) {
return SemIR::ErrorInst::TypeId;
}
}
return arg_type_id;
}
auto CheckFunctionDefinitionSignature(Context& context,
SemIR::FunctionId function_id) -> void {
auto& function = context.functions().Get(function_id);
auto params_to_complete =
context.inst_blocks().GetOrEmpty(function.call_params_id);
// The return parameter will be diagnosed after and differently from other
// parameters.
auto return_call_param = SemIR::InstId::None;
if (!params_to_complete.empty() && function.return_pattern_id.has_value()) {
return_call_param = params_to_complete.consume_back();
}
// Check the parameter types are complete.
for (auto param_ref_id : params_to_complete) {
if (param_ref_id == SemIR::ErrorInst::InstId) {
continue;
}
// The parameter types need to be complete.
RequireCompleteType(
context, context.insts().Get(param_ref_id).type_id(),
SemIR::LocId(param_ref_id), [&](auto& builder) {
CARBON_DIAGNOSTIC(
IncompleteTypeInFunctionParam, Context,
"parameter has incomplete type {0} in function definition",
TypeOfInstId);
builder.Context(param_ref_id, IncompleteTypeInFunctionParam,
param_ref_id);
});
}
// Check the return type is complete.
if (function.return_pattern_id.has_value()) {
CheckFunctionReturnPatternType(
context, SemIR::LocId(function.return_pattern_id),
function.return_pattern_id, SemIR::SpecificId::None);
// `CheckFunctionReturnPatternType` should have diagnosed incomplete types,
// so don't `RequireCompleteType` on the return type.
if (return_call_param.has_value()) {
// TODO: If the types are already checked for completeness then this does
// nothing?
TryToCompleteType(context,
context.insts().Get(return_call_param).type_id(),
SemIR::LocId(return_call_param));
}
}
}
auto StartFunctionSignature(Context& context) -> void {
context.scope_stack().PushForDeclName();
context.inst_block_stack().Push();
context.pattern_block_stack().Push();
context.full_pattern_stack().PushParameterizedDecl();
}
auto FinishFunctionSignature(Context& context, bool check_unused)
-> FinishFunctionSignatureResult {
context.full_pattern_stack().PopFullPattern();
auto pattern_block_id = context.pattern_block_stack().Pop();
auto decl_block_id = context.inst_block_stack().Pop();
context.scope_stack().Pop(check_unused);
return {.pattern_block_id = pattern_block_id, .decl_block_id = decl_block_id};
}
auto MakeFunctionDecl(Context& context, SemIR::LocId loc_id,
SemIR::InstBlockId decl_block_id, bool build_generic,
bool is_definition, SemIR::Function function)
-> std::pair<SemIR::InstId, SemIR::FunctionId> {
CARBON_CHECK(!function.first_owning_decl_id.has_value());
SemIR::FunctionDecl function_decl = {SemIR::TypeId::None,
SemIR::FunctionId::None, decl_block_id};
auto decl_id = AddPlaceholderInstInNoBlock(
context, SemIR::LocIdAndInst::RuntimeVerified(context.sem_ir(), loc_id,
function_decl));
function.first_owning_decl_id = decl_id;
if (is_definition) {
function.definition_id = decl_id;
}
if (build_generic) {
function.generic_id = BuildGenericDecl(context, decl_id);
}
// Create the `Function` object.
function_decl.function_id = context.functions().Add(std::move(function));
function_decl.type_id =
GetFunctionType(context, function_decl.function_id,
build_generic ? context.scope_stack().PeekSpecificId()
: SemIR::SpecificId::None);
ReplaceInstBeforeConstantUse(context, decl_id, function_decl);
return {decl_id, function_decl.function_id};
}
auto StartFunctionDefinition(Context& context, SemIR::InstId decl_id,
SemIR::FunctionId function_id) -> void {
// Create the function scope and the entry block.
context.scope_stack().PushForFunctionBody(decl_id);
context.inst_block_stack().Push();
context.observe_stack().PushArray();
context.region_stack().PushRegion(context.inst_block_stack().PeekOrAdd());
StartGenericDefinition(context,
context.functions().Get(function_id).generic_id);
CheckFunctionDefinitionSignature(context, function_id);
}
auto FinishFunctionDefinition(Context& context, SemIR::FunctionId function_id)
-> void {
context.inst_block_stack().Pop();
// Any cleanups for a function will have been handled when emitting `return`s.
context.scope_stack().DiscardCleanupsSince(
context.scope_stack().function_cleanup_scope_depth());
context.scope_stack().Pop(/*check_unused=*/true);
auto observe_block_id =
context.observe_blocks().Add(context.observe_stack().PeekArray());
context.observe_stack().PopArray();
auto& function = context.functions().Get(function_id);
function.body_block_ids = context.region_stack().PopRegion();
function.observe_block_id = observe_block_id;
// If this is a generic function, collect information about the definition.
FinishGenericDefinition(context, function.generic_id);
}
} // namespace Carbon::Check