From 53b7cfbaba224206b97d26fa2aa97a4dc9210260 Mon Sep 17 00:00:00 2001 From: Lucile Rose Nihlen Date: Tue, 22 Sep 2026 22:07:35 +0000 Subject: [PATCH] Add basic caller-side support for default values in check (#7800) Modifies the arity check to include a lower-bound for arguments. Adds logic to pattern matching to supply default arguments for missing parameters. --- toolchain/check/call.cpp | 33 +- toolchain/check/convert.cpp | 5 +- toolchain/check/handle_function.cpp | 283 ++++++++--------- toolchain/check/import_ref.cpp | 1 + toolchain/check/merge.cpp | 11 +- toolchain/check/pattern_match.cpp | 46 ++- .../function/call/default_values.carbon | 284 ++++++++++++++++++ toolchain/sem_ir/entity_with_params_base.h | 4 + 8 files changed, 508 insertions(+), 159 deletions(-) create mode 100644 toolchain/check/testdata/function/call/default_values.carbon diff --git a/toolchain/check/call.cpp b/toolchain/check/call.cpp index 7545e98b219d..dc649ad0da09 100644 --- a/toolchain/check/call.cpp +++ b/toolchain/check/call.cpp @@ -42,9 +42,13 @@ enum class EntityKind : uint8_t { } // namespace // Resolves the callee expression in a call to a specific callee, or diagnoses -// if no specific callee can be identified. This verifies the arity of the -// callee and determines any compile-time arguments, but doesn't check that the -// runtime arguments are convertible to the parameter types. +// if no specific callee can be identified. This determines any compile-time +// arguments, but doesn't check that the runtime arguments are convertible to +// the parameter types. It also verifies that the number of arguments is within +// the range [callee_arity - arity_lower_bound_margin, callee_arity]. This +// allows arity matching when the callee has default arguments for some +// subpatterns. In all other cases supply the default value `0` for exact arity +// checking. // // `self_id` and `arg_ids` are the self argument and explicit arguments in the // call. @@ -56,14 +60,21 @@ static auto ResolveCalleeInCall(Context& context, SemIR::LocId loc_id, EntityKind entity_kind_for_diagnostic, SemIR::SpecificId enclosing_specific_id, SemIR::InstId self_id, - llvm::ArrayRef arg_ids) + llvm::ArrayRef arg_ids, + int32_t arity_lower_bound_margin = 0) -> std::optional { - // Check that the arity matches the explicit arguments. + // Check that the arity exactly matches or is the upper bound of the explicit + // arguments. auto param_patterns = context.inst_blocks().GetOrEmpty(entity.param_patterns_id); size_t expected_args_size = param_patterns.size() - (self_id.has_value() ? 1 : 0); - if (arg_ids.size() != expected_args_size) { + CARBON_CHECK(static_cast(arity_lower_bound_margin) <= + expected_args_size); + size_t size_lower_bound = + expected_args_size - static_cast(arity_lower_bound_margin); + if (arg_ids.size() < size_lower_bound || + arg_ids.size() > expected_args_size) { CARBON_DIAGNOSTIC(CallArgCountMismatch, Error, "{0} argument{0:s} passed to " "{1:=0:function|=1:generic class|=2:generic " @@ -219,11 +230,13 @@ auto PerformCallToFunction(Context& context, SemIR::LocId loc_id, llvm::ArrayRef arg_ids, bool is_desugared) -> SemIR::InstId { // If the callee is a generic function, determine the generic argument values - // for the call. + // for the call. Also check the arity of the function against the arguments, + // with allowance for default argument values. + const auto& function = context.functions().Get(callee_function.function_id); auto callee_specific_id = ResolveCalleeInCall( - context, loc_id, context.functions().Get(callee_function.function_id), - EntityKind::Function, callee_function.enclosing_specific_id, - callee_function.self_id, arg_ids); + context, loc_id, function, EntityKind::Function, + callee_function.enclosing_specific_id, callee_function.self_id, arg_ids, + function.default_value_arity); if (!callee_specific_id) { return SemIR::ErrorInst::InstId; } diff --git a/toolchain/check/convert.cpp b/toolchain/check/convert.cpp index 7e1f39df9598..a7d9f9da68af 100644 --- a/toolchain/check/convert.cpp +++ b/toolchain/check/convert.cpp @@ -2382,9 +2382,10 @@ auto ConvertCallArgs(Context& context, SemIR::InstId self_id, SemIR::InstId return_arg_id, const SemIR::Function& callee, SemIR::SpecificId callee_specific_id, bool is_desugared) -> SemIR::InstBlockId { - // The caller should have ensured this callee has the right arity. + // The caller should have ensured this callee has the right arity, modulo + // default arguments. CARBON_CHECK( - (self_id.has_value() ? 1 : 0) + arg_refs.size() == + (self_id.has_value() ? 1 : 0) + arg_refs.size() <= context.inst_blocks().GetOrEmpty(callee.param_patterns_id).size()); return CallerPatternMatch(context, callee_specific_id, callee.self_param_id, diff --git a/toolchain/check/handle_function.cpp b/toolchain/check/handle_function.cpp index 20efd3fdc83f..b3d8a4b6599f 100644 --- a/toolchain/check/handle_function.cpp +++ b/toolchain/check/handle_function.cpp @@ -387,6 +387,152 @@ static auto DiagnoseDefaultValuesNotSpecified( } } +// For the top-level parameter patterns list, and for any level of nested tuple +// patterns, ensure that if a subpattern provides a default value, all +// subsequent patterns at that level of nesting must provide a default value as +// well. Returns the number of default values provided at the top level of the +// function parameter, useful for efficient arity checking in callers later on. +// +// TODO: per https://github.com/carbon-language/carbon-lang/issues/7529, this +// should also consider automatically supplied defaults for fully-specified +// tuple subpatterns, and consider them as having a default for the purposes +// of the out-of-order detection. It will also need to detect the error +// condition when a default is also specified for those fully-specified tuple +// subpatterns. +static auto CheckDefaults(Context& context, SemIR::Function& function) + -> int32_t { + if (!function.param_patterns_id.has_value()) { + return 0; + } + + struct PatternLevelState { + // The inst ids of the subpatterns on this level of tuple subpattern + // nesting, treated as a work list, so in reverse order of declaration. + llvm::SmallVector subpattern_ids; + + // If patterns at this level of nesting have default values, this refers + // to the first instruction to specify a default, useful for diagnostics. + SemIR::InstId first_pattern_with_default = SemIR::InstId::None; + + // If we encounter a tuple-pattern during processing, we suspend processing + // of this pattern level, in the middle of processing a single pattern from + // root to leaves. So we record the current state of processing of a single + // pattern to return to it after processing any tuple subpatterns. + + // True if the current pattern being processed has a default value + // specified. + bool current_pattern_has_default = false; + + // The current pattern we are processing, stored separately since it's been + // popped from the `pattern_work_list` and already processed, just may need + // subsequent processing. + SemIR::InstId current_id = SemIR::InstId::None; + + // A work list of patterns to be processed at this level of nesting. + llvm::SmallVector pattern_work_list; + + // A list of subpatterns missing required defaults, to coalesce error + // reporting into a single diagnostic. + llvm::SmallVector patterns_missing_defaults; + + // A count of the number of patterns on this level that have defaults. + int32_t default_count = 0; + }; + + llvm::SmallVector level_state_stack; + size_t default_count = 0; + level_state_stack.push_back({}); + llvm::append_range( + level_state_stack.back().subpattern_ids, + llvm::reverse(context.inst_blocks().Get(function.param_patterns_id))); + + while (!level_state_stack.empty()) { + PatternLevelState* state = &level_state_stack.back(); + while (!state->subpattern_ids.empty() || + !state->pattern_work_list.empty() || state->current_id.has_value()) { + // If we're not resuming processing a pattern from a nested state, start + // processing the next subpattern. + if (!state->current_id.has_value()) { + state->pattern_work_list.push_back( + state->subpattern_ids.pop_back_val()); + state->current_pattern_has_default = false; + } + while (!state->pattern_work_list.empty()) { + state->current_id = state->pattern_work_list.pop_back_val(); + auto inst = context.insts().Get(state->current_id); + CARBON_KIND_SWITCH(inst) { + case CARBON_KIND(SemIR::DefaultValuePattern default_value_pattern): { + state->current_pattern_has_default = true; + state->default_count += 1; + state->pattern_work_list.push_back( + default_value_pattern.subpattern_id); + break; + } + case CARBON_KIND( + SemIR::WrapperBindingPattern wrapper_binding_pattern): { + state->pattern_work_list.push_back( + wrapper_binding_pattern.subpattern_id); + break; + } + case CARBON_KIND(SemIR::TuplePattern tuple_pattern): { + auto elements = + context.inst_blocks().Get(tuple_pattern.elements_id); + if (!elements.empty()) { + // Start a new state for the nested tuple pattern elements. + level_state_stack.push_back({}); + state = &level_state_stack.back(); + llvm::append_range(state->subpattern_ids, + llvm::reverse(elements)); + } + break; + } + default: + // We only process patterns containing subpatterns, so this is an + // intentional no-op. + break; + } + } + // Finished processing this subpattern, detect a missing default if + // required. + if (state->current_pattern_has_default && + !state->first_pattern_with_default.has_value()) { + state->first_pattern_with_default = state->current_id; + } else if (!state->current_pattern_has_default && + state->first_pattern_with_default.has_value()) { + state->patterns_missing_defaults.push_back(state->current_id); + } + state->current_id = SemIR::InstId::None; + } + // Finished processing this tuple-pattern, emit diagnostics if any. + if (!state->patterns_missing_defaults.empty()) { + CARBON_DIAGNOSTIC(RequiredPatternDefaultValueMissing, Error, + "this pattern is missing a required default value."); + CARBON_DIAGNOSTIC(RequiredPatternDefaultValueFirstDefault, Note, + "all patterns to the right of this first pattern with " + "a default value must also specify a default value."); + CARBON_DIAGNOSTIC( + RequiredPatternDefaultValueMissingAdditional, Note, + "this pattern is also missing a required default value."); + auto inst_ref = llvm::ArrayRef(state->patterns_missing_defaults); + auto builder = context.emitter().Build( + inst_ref.consume_front(), RequiredPatternDefaultValueMissing); + for (auto inst_id : inst_ref) { + builder.Note(inst_id, RequiredPatternDefaultValueMissingAdditional); + } + builder.Note(state->first_pattern_with_default, + RequiredPatternDefaultValueFirstDefault); + builder.Emit(); + } + + // Extract the count from the level we just completed, overwriting any + // nested level value extracted previously. + default_count = level_state_stack.back().default_count; + level_state_stack.pop_back(); + } + + return default_count; +} + // Build a FunctionDecl describing the signature of a function. This // handles the common logic shared by function declaration syntax and function // definition syntax. @@ -469,6 +615,8 @@ static auto BuildFunctionDecl(Context& context, function_info.definition_id = decl_id; } + function_info.default_value_arity = CheckDefaults(context, function_info); + DiagnosePositionalParams(context, function_info); if (name_context.state != DeclNameStack::NameContext::State::Poisoned && !name_context.prev_inst_id().has_value()) { @@ -597,145 +745,10 @@ static auto DiagnoseUnusedMarkersWithoutDefinition( } } -// For the top-level parameter patterns list, and for any level of nested tuple -// patterns, ensure that if a subpattern provides a default value, all -// subsequent patterns at that level of nesting must provide a default value as -// well. -// TODO: per https://github.com/carbon-language/carbon-lang/issues/7529, this -// should also consider automatically supplied defaults for fully-specified -// tuple subpatterns, and consider them as having a default for the purposes -// of the out-of-order detection. It will also need to detect the error -// condition when a default is also specified for those fully-specified tuple -// subpatterns. -static auto DiagnoseOutOfOrderDefaults(Context& context, - SemIR::FunctionId function_id) -> void { - const auto& function = context.functions().Get(function_id); - if (!function.param_patterns_id.has_value()) { - return; - } - - struct PatternLevelState { - // The inst ids of the subpatterns on this level of tuple subpattern - // nesting, treated as a work list, so in reverse order of declaration. - llvm::SmallVector subpattern_ids; - - // If patterns at this level of nesting have default values, this refers - // to the first instruction to specify a default, useful for diagnostics. - SemIR::InstId first_pattern_with_default = SemIR::InstId::None; - - // If we encounter a tuple-pattern during processing, we suspend processing - // of this pattern level, in the middle of processing a single pattern from - // root to leaves. So we record the current state of processing of a single - // pattern to return to it after processing any tuple subpatterns. - - // True if the current pattern being processed has a default value - // specified. - bool current_pattern_has_default = false; - - // The current pattern we are processing, stored separately since it's been - // popped from the `pattern_work_list` and already processed, just may need - // subsequent processing. - SemIR::InstId current_id = SemIR::InstId::None; - - // A work list of patterns to be processed at this level of nesting. - llvm::SmallVector pattern_work_list; - - // A list of subpatterns missing required defaults, to coalesce error - // reporting into a single diagnostic. - llvm::SmallVector patterns_missing_defaults; - }; - - llvm::SmallVector level_state_stack; - level_state_stack.push_back({}); - llvm::append_range( - level_state_stack.back().subpattern_ids, - llvm::reverse(context.inst_blocks().Get(function.param_patterns_id))); - - while (!level_state_stack.empty()) { - PatternLevelState* state = &level_state_stack.back(); - while (!state->subpattern_ids.empty() || - !state->pattern_work_list.empty() || state->current_id.has_value()) { - // If we're not resuming processing a pattern from a nested state, start - // processing the next subpattern. - if (!state->current_id.has_value()) { - state->pattern_work_list.push_back( - state->subpattern_ids.pop_back_val()); - state->current_pattern_has_default = false; - } - while (!state->pattern_work_list.empty()) { - state->current_id = state->pattern_work_list.pop_back_val(); - auto inst = context.insts().Get(state->current_id); - CARBON_KIND_SWITCH(inst) { - case CARBON_KIND(SemIR::DefaultValuePattern default_value_pattern): { - state->current_pattern_has_default = true; - state->pattern_work_list.push_back( - default_value_pattern.subpattern_id); - break; - } - case CARBON_KIND( - SemIR::WrapperBindingPattern wrapper_binding_pattern): { - state->pattern_work_list.push_back( - wrapper_binding_pattern.subpattern_id); - break; - } - case CARBON_KIND(SemIR::TuplePattern tuple_pattern): { - auto elements = - context.inst_blocks().Get(tuple_pattern.elements_id); - if (!elements.empty()) { - // Start a new state for the nested tuple pattern elements. - level_state_stack.push_back({}); - state = &level_state_stack.back(); - llvm::append_range(state->subpattern_ids, - llvm::reverse(elements)); - } - break; - } - default: - // We only process patterns containing subpatterns, so this is an - // intentional no-op. - break; - } - } - // Finished processing this subpattern, detect a missing default if - // required. - if (state->current_pattern_has_default && - !state->first_pattern_with_default.has_value()) { - state->first_pattern_with_default = state->current_id; - } else if (!state->current_pattern_has_default && - state->first_pattern_with_default.has_value()) { - state->patterns_missing_defaults.push_back(state->current_id); - } - state->current_id = SemIR::InstId::None; - } - // Finished processing this tuple-pattern, emit diagnostics if any. - if (!state->patterns_missing_defaults.empty()) { - CARBON_DIAGNOSTIC(RequiredPatternDefaultValueMissing, Error, - "this pattern is missing a required default value."); - CARBON_DIAGNOSTIC(RequiredPatternDefaultValueFirstDefault, Note, - "all patterns to the right of this first pattern with " - "a default value must also specify a default value."); - CARBON_DIAGNOSTIC( - RequiredPatternDefaultValueMissingAdditional, Note, - "this pattern is also missing a required default value."); - auto inst_ref = llvm::ArrayRef(state->patterns_missing_defaults); - auto builder = context.emitter().Build( - inst_ref.consume_front(), RequiredPatternDefaultValueMissing); - for (auto inst_id : inst_ref) { - builder.Note(inst_id, RequiredPatternDefaultValueMissingAdditional); - } - builder.Note(state->first_pattern_with_default, - RequiredPatternDefaultValueFirstDefault); - builder.Emit(); - } - level_state_stack.pop_back(); - } -} - auto HandleParseNode(Context& context, Parse::FunctionDeclId node_id) -> bool { auto [function_id, decl_id] = BuildFunctionDecl(context, node_id, /*is_definition=*/false); DiagnoseUnusedMarkersWithoutDefinition(context, function_id); - DiagnoseOutOfOrderDefaults(context, function_id); context.decl_name_stack().PopScope(); return true; } diff --git a/toolchain/check/import_ref.cpp b/toolchain/check/import_ref.cpp index d47a3b9890e6..def48f43b4d6 100644 --- a/toolchain/check/import_ref.cpp +++ b/toolchain/check/import_ref.cpp @@ -2661,6 +2661,7 @@ static auto TryResolveTypedInst(ImportRefResolver& resolver, if (import_function.definition_id.has_value()) { new_function.definition_id = new_function.first_owning_decl_id; } + new_function.default_value_arity = import_function.default_value_arity; switch (import_function.special_function_kind) { case SemIR::Function::SpecialFunctionKind::CppThunk: diff --git a/toolchain/check/merge.cpp b/toolchain/check/merge.cpp index 90ed33f7d37e..cc7aacb7c28e 100644 --- a/toolchain/check/merge.cpp +++ b/toolchain/check/merge.cpp @@ -341,12 +341,11 @@ static auto CheckRedeclParam(Context& context, bool is_implicit_param, // If the new pattern specified a default value, it must match the // previously declared default value. - const auto& new_default_value = context.default_values().Get( + auto& new_default_value = context.default_values().Get( new_default_value_pattern.default_value_id); - + const auto& prev_default_value = context.default_values().Get( + prev_default_value_pattern.default_value_id); if (!new_default_value.is_unspecified) { - const auto& prev_default_value = context.default_values().Get( - prev_default_value_pattern.default_value_id); // We require first owning declaration to always specify a default // value. CARBON_CHECK(!prev_default_value.is_unspecified); @@ -358,6 +357,10 @@ static auto CheckRedeclParam(Context& context, bool is_implicit_param, emit_general_diagnostic(); return false; } + } else { + // If the new default value was left unspecified, we copy the previous + // processed default value into the new default value. + new_default_value.value_id = prev_default_value.value_id; } pattern_stack.push_back( diff --git a/toolchain/check/pattern_match.cpp b/toolchain/check/pattern_match.cpp index 0eb3d52ff06b..f0b7ccc70fec 100644 --- a/toolchain/check/pattern_match.cpp +++ b/toolchain/check/pattern_match.cpp @@ -119,7 +119,9 @@ using State = class MatchContext { public: struct PreWork : Printable { - // `None` when processing the callee side. + // `None` when processing the callee side, or when processing the caller + // side and no value was supplied, in expectation of using a default value + // from the corresponding callee pattern. SemIR::InstId scrutinee_id; auto Print(llvm::raw_ostream& out) const -> void { @@ -936,13 +938,31 @@ auto MatchContext::DoPreWork(State state, SemIR::DefaultValuePattern default_value_pattern, SemIR::InstId scrutinee_id, WorkItem entry) -> void { - if (!std::holds_alternative(state)) { - CARBON_FATAL("Unhandled state kind in DefaultValuePattern pre-work"); + CARBON_KIND_SWITCH(state) { + case CARBON_KIND(CallerState* _): { + // If there's no scrutinee supplied, supply the default value instead. + if (!scrutinee_id.has_value()) { + const auto& default_value = context_.default_values().Get( + default_value_pattern.default_value_id); + CARBON_CHECK(default_value.value_id.has_value()); + auto [inst_id, _] = WrapInstForSpecific( + context_, SemIR::LocId(default_value.value_id), + default_value.value_id, specific_id_stack_.back()); + scrutinee_id = inst_id; + } + break; + } + case CARBON_KIND(CalleeState* _): { + // We will need to check the type of the parameter to make sure it + // matches the provided default, so add ourselves to the post-work list. + results_stack_.PushArray(); + AddAsPostWork(entry); + break; + } + default: { + CARBON_FATAL("Unhandled state kind in DefaultValuePattern pre-work"); + } } - // We will need to check the type of the parameter to make sure it - // matches the provided default, so add ourselves to the post-work list. - results_stack_.PushArray(); - AddAsPostWork(entry); // Process the subpattern for the default. AddWork({.pattern_id = default_value_pattern.subpattern_id, @@ -1226,9 +1246,19 @@ auto CallerPatternMatch(Context& context, SemIR::SpecificId specific_id, CARBON_CHECK(self_pattern_id.has_value()); } - for (const auto& [arg_id, param_pattern_id] : llvm::zip_equal( + // `arg_refs` may have a smaller arity than `param_patterns_id` due to the + // possible presence of default values for some parameters. We use + // `zip_longest` here to allow for that size disparity. But we presume we + // always have a parameter pattern, so test that presumption here. + CARBON_CHECK(self_arg_refs.size() + arg_refs.size() <= + context.inst_blocks().GetOrEmpty(param_patterns_id).size()); + for (const auto& [maybe_arg_id, maybe_param_pattern_id] : llvm::zip_longest( llvm::concat(self_arg_refs, arg_refs), context.inst_blocks().GetOrEmpty(param_patterns_id))) { + CARBON_CHECK(maybe_param_pattern_id.has_value()); + const auto& param_pattern_id = *maybe_param_pattern_id; + const auto& arg_id = + maybe_arg_id.has_value() ? *maybe_arg_id : SemIR::InstId::None; match.Match(&state, {.pattern_id = param_pattern_id, .work = MatchContext::PreWork{.scrutinee_id = arg_id}, diff --git a/toolchain/check/testdata/function/call/default_values.carbon b/toolchain/check/testdata/function/call/default_values.carbon new file mode 100644 index 000000000000..3d526593fc50 --- /dev/null +++ b/toolchain/check/testdata/function/call/default_values.carbon @@ -0,0 +1,284 @@ +// 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-FILE: toolchain/testing/testdata/min_prelude/destroy.carbon +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/function/call/default_values.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/function/call/default_values.carbon + +// --- fail_caller_too_many_arguments.carbon +library "[[@TEST_NAME]]"; +class C {} + +fn F(unused x: C = {}, unused y: C = {}, unused z: C = {}) {} +fn H(unused x: C = {}, (unused y: C = {}, unused z: C = {}) = ({}, {})) {} + +fn G() { + // CHECK:STDERR: fail_caller_too_many_arguments.carbon:[[@LINE+14]]:3: error: 4 arguments passed to function expecting 3 arguments [CallArgCountMismatch] + // CHECK:STDERR: F({}, {}, {}, {}); + // CHECK:STDERR: ^~~~~~~~~~~~~~~~~ + // CHECK:STDERR: fail_caller_too_many_arguments.carbon:[[@LINE-7]]:1: note: calling function declared here [InCallToEntity] + // CHECK:STDERR: fn F(unused x: C = {}, unused y: C = {}, unused z: C = {}) {} + // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // CHECK:STDERR: + // CHECK:STDERR: fail_caller_too_many_arguments.carbon:[[@LINE-10]]:24: error: tuple pattern expects 2 elements, but tuple literal has 3 [TuplePatternSizeDoesntMatchLiteral] + // CHECK:STDERR: fn H(unused x: C = {}, (unused y: C = {}, unused z: C = {}) = ({}, {})) {} + // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // CHECK:STDERR: fail_caller_too_many_arguments.carbon:[[@LINE-13]]:24: note: initializing function parameter [InCallToFunctionParam] + // CHECK:STDERR: fn H(unused x: C = {}, (unused y: C = {}, unused z: C = {}) = ({}, {})) {} + // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // CHECK:STDERR: + F({}, {}, {}, {}); + H({}, ({}, {}, {})); +} + +// --- fail_caller_too_few_arguments.carbon +library "[[@TEST_NAME]]"; +class C {} + +fn F(unused x: C, unused y: C = {}) {} + +fn G() { + // CHECK:STDERR: fail_caller_too_few_arguments.carbon:[[@LINE+7]]:3: error: 0 arguments passed to function expecting 2 arguments [CallArgCountMismatch] + // CHECK:STDERR: F(); + // CHECK:STDERR: ^~~ + // CHECK:STDERR: fail_caller_too_few_arguments.carbon:[[@LINE-6]]:1: note: calling function declared here [InCallToEntity] + // CHECK:STDERR: fn F(unused x: C, unused y: C = {}) {} + // CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // CHECK:STDERR: + F(); +} + +// --- imported_callee.carbon +library "[[@TEST_NAME]]"; +class C {} + +fn F(x: C, y: C = {}); + +// --- imported_callee.impl.carbon +impl library "[[@TEST_NAME]]"; + +fn F(unused x: C, unused y: C = _) {} + +//@dump-sem-ir-begin +fn G() { + F({}); +} +//@dump-sem-ir-end + + +// --- tuple_defaults_fully_specified.carbon +library "[[@TEST_NAME]]"; +class C {} + +//@dump-sem-ir-begin +fn F(unused x: C, (unused y: C, unused z: C) = ({}, {}), unused w: C = {}) {} + +fn G() { + F({}); +} +//@dump-sem-ir-end + +// --- single_argument.carbon +library "[[@TEST_NAME]]"; +class C {} + +fn F(unused x: C = {}) {} + +//@dump-sem-ir-begin +fn G() { + F(); +} +//@dump-sem-ir-end + + +// CHECK:STDOUT: --- imported_callee.impl.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: type: type = facet_type [concrete] +// CHECK:STDOUT: %C: type = class_type @C [concrete] +// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [concrete] +// CHECK:STDOUT: %F.type: type = fn_type @F [concrete] +// CHECK:STDOUT: %F: %F.type = struct_value () [concrete] +// CHECK:STDOUT: %C.val: %C = struct_value () [concrete] +// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete] +// CHECK:STDOUT: %G.type: type = fn_type @G [concrete] +// CHECK:STDOUT: %G: %G.type = struct_value () [concrete] +// CHECK:STDOUT: %empty_struct: %empty_struct_type = struct_value () [concrete] +// CHECK:STDOUT: %.115: ref %C = temporary invalid, %C.val [concrete] +// CHECK:STDOUT: %Destroy.WithSelf.Op.type.ef016f.2: type = fn_type @Destroy.WithSelf.Op.loc7_6.2 [concrete] +// CHECK:STDOUT: %Destroy.WithSelf.Op.403171.2: %Destroy.WithSelf.Op.type.ef016f.2 = struct_value () [concrete] +// CHECK:STDOUT: %Destroy.WithSelf.Op.bound: = bound_method %.115, %Destroy.WithSelf.Op.403171.2 [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: imports { +// CHECK:STDOUT: %Main.import_ref.d96: %C = import_ref Main//imported_callee, loc4_20, loaded [concrete = constants.%C.val] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: %G.decl: %G.type = fn_decl @G [concrete = constants.%G] {} {} +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @G() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %F.ref: %F.type = name_ref F, file.%F.decl [concrete = constants.%F] +// CHECK:STDOUT: %.loc7_6.1: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct] +// CHECK:STDOUT: %.loc7_6.2: ref %C = temporary_storage +// CHECK:STDOUT: %.loc7_6.3: init %C to %.loc7_6.2 = class_init () [concrete = constants.%C.val] +// CHECK:STDOUT: %.loc7_6.4: init %C = converted %.loc7_6.1, %.loc7_6.3 [concrete = constants.%C.val] +// CHECK:STDOUT: %.loc7_6.5: ref %C = temporary %.loc7_6.2, %.loc7_6.4 [concrete = constants.%.115] +// CHECK:STDOUT: %.loc7_6.6: %C = acquire_value %.loc7_6.5 [concrete = constants.%C.val] +// CHECK:STDOUT: %F.call: init %empty_tuple.type = call %F.ref(%.loc7_6.6, imports.%Main.import_ref.d96) +// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call constants.%Destroy.WithSelf.Op.bound(constants.%.115) +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc7_6.1(%self.param: ref %empty_struct_type) = "no_op"; +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc7_6.2(%self.param: ref %C) { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: --- tuple_defaults_fully_specified.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: type: type = facet_type [concrete] +// CHECK:STDOUT: %C: type = class_type @C [concrete] +// CHECK:STDOUT: %empty_struct_type: type = struct_type {} [concrete] +// CHECK:STDOUT: %pattern_type.98b: type = pattern_type %C [concrete] +// CHECK:STDOUT: %x.param_patt: %pattern_type.98b = value_param_pattern [concrete] +// CHECK:STDOUT: %x.patt: %pattern_type.98b = wrapper_binding_pattern x, %x.param_patt [concrete] +// CHECK:STDOUT: %y.param_patt: %pattern_type.98b = value_param_pattern [concrete] +// CHECK:STDOUT: %y.patt: %pattern_type.98b = wrapper_binding_pattern y, %y.param_patt [concrete] +// CHECK:STDOUT: %z.param_patt: %pattern_type.98b = value_param_pattern [concrete] +// CHECK:STDOUT: %z.patt: %pattern_type.98b = wrapper_binding_pattern z, %z.param_patt [concrete] +// CHECK:STDOUT: %tuple.type.748: type = tuple_type (%C, %C) [concrete] +// CHECK:STDOUT: %pattern_type.8b4: type = pattern_type %tuple.type.748 [concrete] +// CHECK:STDOUT: %.2bb: %pattern_type.8b4 = tuple_pattern (%y.patt, %z.patt) [concrete] +// CHECK:STDOUT: %empty_struct: %empty_struct_type = struct_value () [concrete] +// CHECK:STDOUT: %tuple.type.b6b: type = tuple_type (%empty_struct_type, %empty_struct_type) [concrete] +// CHECK:STDOUT: %tuple.9a3: %tuple.type.b6b = tuple_value (%empty_struct, %empty_struct) [concrete] +// CHECK:STDOUT: %.e69: %pattern_type.8b4 = default_value_pattern %.2bb, @F.%.loc5_55.8 [concrete] +// CHECK:STDOUT: %w.param_patt: %pattern_type.98b = value_param_pattern [concrete] +// CHECK:STDOUT: %w.patt: %pattern_type.98b = wrapper_binding_pattern w, %w.param_patt [concrete] +// CHECK:STDOUT: %.692: %pattern_type.98b = default_value_pattern %w.patt, @F.%.loc5_73.6 [concrete] +// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete] +// CHECK:STDOUT: %C.val: %C = struct_value () [concrete] +// CHECK:STDOUT: %.115: ref %C = temporary invalid, %C.val [concrete] +// CHECK:STDOUT: %tuple.d3e: %tuple.type.748 = tuple_value (%C.val, %C.val) [concrete] +// CHECK:STDOUT: %F.type: type = fn_type @F [concrete] +// CHECK:STDOUT: %F: %F.type = struct_value () [concrete] +// CHECK:STDOUT: %G.type: type = fn_type @G [concrete] +// CHECK:STDOUT: %G: %G.type = struct_value () [concrete] +// CHECK:STDOUT: %Destroy.WithSelf.Op.type.ef016f.2: type = fn_type @Destroy.WithSelf.Op.loc8_6.2 [concrete] +// CHECK:STDOUT: %Destroy.WithSelf.Op.403171.2: %Destroy.WithSelf.Op.type.ef016f.2 = struct_value () [concrete] +// CHECK:STDOUT: %Destroy.WithSelf.Op.bound: = bound_method %.115, %Destroy.WithSelf.Op.403171.2 [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [concrete = constants.%F] { +// CHECK:STDOUT: %x.param_patt: %pattern_type.98b = value_param_pattern [concrete = constants.%x.param_patt] +// CHECK:STDOUT: %x.patt: %pattern_type.98b = wrapper_binding_pattern x, %x.param_patt [concrete = constants.%x.patt] +// CHECK:STDOUT: %y.param_patt: %pattern_type.98b = value_param_pattern [concrete = constants.%y.param_patt] +// CHECK:STDOUT: %y.patt: %pattern_type.98b = wrapper_binding_pattern y, %y.param_patt [concrete = constants.%y.patt] +// CHECK:STDOUT: %z.param_patt: %pattern_type.98b = value_param_pattern [concrete = constants.%z.param_patt] +// CHECK:STDOUT: %z.patt: %pattern_type.98b = wrapper_binding_pattern z, %z.param_patt [concrete = constants.%z.patt] +// CHECK:STDOUT: %.loc5_44: %pattern_type.8b4 = tuple_pattern (%y.patt, %z.patt) [concrete = constants.%.2bb] +// CHECK:STDOUT: %.loc5_46: %pattern_type.8b4 = default_value_pattern %.loc5_44, %.loc5_55.8 [concrete = constants.%.e69] +// CHECK:STDOUT: %w.param_patt: %pattern_type.98b = value_param_pattern [concrete = constants.%w.param_patt] +// CHECK:STDOUT: %w.patt: %pattern_type.98b = wrapper_binding_pattern w, %w.param_patt [concrete = constants.%w.patt] +// CHECK:STDOUT: %.loc5_70: %pattern_type.98b = default_value_pattern %w.patt, %.loc5_73.6 [concrete = constants.%.692] +// CHECK:STDOUT: } { +// CHECK:STDOUT: %.loc5_50.1: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct] +// CHECK:STDOUT: %.loc5_54.1: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct] +// CHECK:STDOUT: %.loc5_55.1: %tuple.type.b6b = tuple_literal (%.loc5_50.1, %.loc5_54.1) [concrete = constants.%tuple.9a3] +// CHECK:STDOUT: %.loc5_73.1: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct] +// CHECK:STDOUT: %x.param: %C = value_param call_param0 +// CHECK:STDOUT: %C.ref.loc5_16: type = name_ref C, file.%C.decl [concrete = constants.%C] +// CHECK:STDOUT: %x: %C = wrapper_binding x, %x.param +// CHECK:STDOUT: %y.param: %C = value_param call_param1 +// CHECK:STDOUT: %C.ref.loc5_30: type = name_ref C, file.%C.decl [concrete = constants.%C] +// CHECK:STDOUT: %y: %C = wrapper_binding y, %y.param +// CHECK:STDOUT: %z.param: %C = value_param call_param2 +// CHECK:STDOUT: %C.ref.loc5_43: type = name_ref C, file.%C.decl [concrete = constants.%C] +// CHECK:STDOUT: %z: %C = wrapper_binding z, %z.param +// CHECK:STDOUT: %tuple.loc5_44: %tuple.type.748 = tuple_value (%y.param, %z.param) +// CHECK:STDOUT: %.loc5_50.2: ref %C = temporary_storage +// CHECK:STDOUT: %.loc5_50.3: init %C to %.loc5_50.2 = class_init () [concrete = constants.%C.val] +// CHECK:STDOUT: %.loc5_55.2: init %C = converted %.loc5_50.1, %.loc5_50.3 [concrete = constants.%C.val] +// CHECK:STDOUT: %.loc5_55.3: ref %C = temporary %.loc5_50.2, %.loc5_55.2 [concrete = constants.%.115] +// CHECK:STDOUT: %.loc5_55.4: %C = acquire_value %.loc5_55.3 [concrete = constants.%C.val] +// CHECK:STDOUT: %.loc5_54.2: ref %C = temporary_storage +// CHECK:STDOUT: %.loc5_54.3: init %C to %.loc5_54.2 = class_init () [concrete = constants.%C.val] +// CHECK:STDOUT: %.loc5_55.5: init %C = converted %.loc5_54.1, %.loc5_54.3 [concrete = constants.%C.val] +// CHECK:STDOUT: %.loc5_55.6: ref %C = temporary %.loc5_54.2, %.loc5_55.5 [concrete = constants.%.115] +// CHECK:STDOUT: %.loc5_55.7: %C = acquire_value %.loc5_55.6 [concrete = constants.%C.val] +// CHECK:STDOUT: %tuple.loc5_55: %tuple.type.748 = tuple_value (%.loc5_55.4, %.loc5_55.7) [concrete = constants.%tuple.d3e] +// CHECK:STDOUT: %.loc5_55.8: %tuple.type.748 = converted %.loc5_55.1, %tuple.loc5_55 [concrete = constants.%tuple.d3e] +// CHECK:STDOUT: %w.param: %C = value_param call_param3 +// CHECK:STDOUT: %C.ref.loc5_68: type = name_ref C, file.%C.decl [concrete = constants.%C] +// CHECK:STDOUT: %w: %C = wrapper_binding w, %w.param +// CHECK:STDOUT: %.loc5_73.2: ref %C = temporary_storage +// CHECK:STDOUT: %.loc5_73.3: init %C to %.loc5_73.2 = class_init () [concrete = constants.%C.val] +// CHECK:STDOUT: %.loc5_73.4: init %C = converted %.loc5_73.1, %.loc5_73.3 [concrete = constants.%C.val] +// CHECK:STDOUT: %.loc5_73.5: ref %C = temporary %.loc5_73.2, %.loc5_73.4 [concrete = constants.%.115] +// CHECK:STDOUT: %.loc5_73.6: %C = acquire_value %.loc5_73.5 [concrete = constants.%C.val] +// CHECK:STDOUT: } +// CHECK:STDOUT: %G.decl: %G.type = fn_decl @G [concrete = constants.%G] {} {} +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F(%x.param: %C, %y.param: %C, %z.param: %C, %w.param: %C) { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @G() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %F.ref: %F.type = name_ref F, file.%F.decl [concrete = constants.%F] +// CHECK:STDOUT: %.loc8_6.1: %empty_struct_type = struct_literal () [concrete = constants.%empty_struct] +// CHECK:STDOUT: %.loc8_6.2: ref %C = temporary_storage +// CHECK:STDOUT: %.loc8_6.3: init %C to %.loc8_6.2 = class_init () [concrete = constants.%C.val] +// CHECK:STDOUT: %.loc8_6.4: init %C = converted %.loc8_6.1, %.loc8_6.3 [concrete = constants.%C.val] +// CHECK:STDOUT: %.loc8_6.5: ref %C = temporary %.loc8_6.2, %.loc8_6.4 [concrete = constants.%.115] +// CHECK:STDOUT: %.loc8_6.6: %C = acquire_value %.loc8_6.5 [concrete = constants.%C.val] +// CHECK:STDOUT: %tuple.elem0: %C = tuple_access @F.%.loc5_55.8, element0 [concrete = constants.%C.val] +// CHECK:STDOUT: %tuple.elem1: %C = tuple_access @F.%.loc5_55.8, element1 [concrete = constants.%C.val] +// CHECK:STDOUT: %F.call: init %empty_tuple.type = call %F.ref(%.loc8_6.6, %tuple.elem0, %tuple.elem1, @F.%.loc5_73.6) +// CHECK:STDOUT: %Destroy.WithSelf.Op.call: init %empty_tuple.type = call constants.%Destroy.WithSelf.Op.bound(constants.%.115) +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc8_6.1(%self.param: ref %empty_struct_type) = "no_op"; +// CHECK:STDOUT: +// CHECK:STDOUT: fn @Destroy.WithSelf.Op.loc8_6.2(%self.param: ref %C) { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: --- single_argument.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: type: type = facet_type [concrete] +// CHECK:STDOUT: %empty_tuple.type: type = tuple_type () [concrete] +// CHECK:STDOUT: %F.type: type = fn_type @F [concrete] +// CHECK:STDOUT: %F: %F.type = struct_value () [concrete] +// CHECK:STDOUT: %G.type: type = fn_type @G [concrete] +// CHECK:STDOUT: %G: %G.type = struct_value () [concrete] +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: file { +// CHECK:STDOUT: %G.decl: %G.type = fn_decl @G [concrete = constants.%G] {} {} +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @G() { +// CHECK:STDOUT: !entry: +// CHECK:STDOUT: %F.ref: %F.type = name_ref F, file.%F.decl [concrete = constants.%F] +// CHECK:STDOUT: %F.call: init %empty_tuple.type = call %F.ref(@F.%.loc4_21.6) +// CHECK:STDOUT: return +// CHECK:STDOUT: } +// CHECK:STDOUT: diff --git a/toolchain/sem_ir/entity_with_params_base.h b/toolchain/sem_ir/entity_with_params_base.h index c8c0a8933aac..ea6aa84db556 100644 --- a/toolchain/sem_ir/entity_with_params_base.h +++ b/toolchain/sem_ir/entity_with_params_base.h @@ -133,6 +133,10 @@ struct EntityWithParamsBase { // The definition of the entity. This will be a Decl. InstId definition_id = InstId::None; + + // A count of the top-level explicit parameters that have default values + // provided. This is useful for quickly bounds-checking caller argument arity. + int32_t default_value_arity = 0; }; } // namespace Carbon::SemIR