diff --git a/toolchain/check/cpp/import.cpp b/toolchain/check/cpp/import.cpp index f11b8b4ab9fc..5d1c8eeea577 100644 --- a/toolchain/check/cpp/import.cpp +++ b/toolchain/check/cpp/import.cpp @@ -1963,6 +1963,7 @@ static auto ImportFunction(Context& context, SemIR::LocId loc_id, .call_param_patterns_id = function_params_insts->call_param_patterns_id, .call_params_id = function_params_insts->call_params_id, + .call_param_default_values_id = SemIR::InstBlockId::None, .call_param_ranges = function_params_insts->param_ranges, .return_type_inst_id = function_params_insts->return_type_inst_id, .return_form_inst_id = function_params_insts->return_form_inst_id, diff --git a/toolchain/check/full_pattern_stack.h b/toolchain/check/full_pattern_stack.h index 677d26dc3443..75cab047491c 100644 --- a/toolchain/check/full_pattern_stack.h +++ b/toolchain/check/full_pattern_stack.h @@ -8,6 +8,7 @@ #include "common/array_stack.h" #include "common/check.h" #include "toolchain/check/lexical_lookup.h" +#include "toolchain/sem_ir/id_kind.h" #include "toolchain/sem_ir/ids.h" namespace Carbon::Check { @@ -120,6 +121,7 @@ class FullPatternStack { CARBON_CHECK(kind_stack_.back() == Kind::NotInEitherParamList, "{0}", kind_stack_.back()); kind_stack_.back() = Kind::ExplicitParamList; + default_values_stack_.PushArray(); } // Marks the end of the current parameterized entity's explicit parameter @@ -139,6 +141,7 @@ class FullPatternStack { // Marks the end of checking and pattern matching for the current // full-pattern. auto PopFullPattern() -> void { + auto kind = kind_stack_.back(); kind_stack_.pop_back(); bind_name_stack_.PopArray(); int index = next_var_index_stack_.pop_back_val(); @@ -146,6 +149,9 @@ class FullPatternStack { var_pattern_stack_.PeekArray().size(), "`GetLocalVarStorage` not called for all var patterns"); var_pattern_stack_.PopArray(); + if (kind == Kind::ExplicitParamList) { + default_values_stack_.PopArray(); + } } // Records that `name_id` was introduced by the current full-pattern. @@ -200,6 +206,26 @@ class FullPatternStack { kind_stack_.size()); } + // Adds the inst id for a constant value provided as a default value for + // any subpattern in the full-pattern. Returns the index of that element + // as a `DefaultValueId`. Note default values are only supported for + // explicit parameter lists. + auto AddDefaultValue(SemIR::InstId inst_id) -> SemIR::DefaultValueId { + auto index = SemIR::FromRaw( + static_cast(default_values_stack_.PeekArray().size())); + CARBON_CHECK(kind_stack_.back() == Kind::ExplicitParamList); + default_values_stack_.AppendToTop(inst_id); + return index; + } + + // Returns a reference to the array of default value inst ids at the top of + // the stack. Note default values are only supported for explicit parameter + // lists. + auto GetDefaultValues() -> llvm::ArrayRef { + CARBON_CHECK(!default_values_stack_.empty()); + return default_values_stack_.PeekArray(); + } + private: LexicalLookup* lookup_; @@ -231,6 +257,10 @@ class FullPatternStack { // the corresponding frame of `var_pattern_stack_`, or -1 if the contents // of that frame are not ready for consumption. llvm::SmallVector next_var_index_stack_; + + // The stack of instructions specifying default values for subpatterns + // within this full-pattern. + ArrayStack default_values_stack_; }; } // namespace Carbon::Check diff --git a/toolchain/check/function.cpp b/toolchain/check/function.cpp index 84ce627f00e6..480e3af3446e 100644 --- a/toolchain/check/function.cpp +++ b/toolchain/check/function.cpp @@ -90,6 +90,7 @@ struct FunctionSignatureInsts { 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; @@ -150,6 +151,7 @@ static auto MakeFunctionSignature(Context& context, SemIR::LocId loc_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] = @@ -188,6 +190,8 @@ auto MakeGeneratedFunctionDecl(Context& context, SemIR::LocId loc_id, { .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, diff --git a/toolchain/check/global_init.cpp b/toolchain/check/global_init.cpp index 561330520441..25700d0a35f0 100644 --- a/toolchain/check/global_init.cpp +++ b/toolchain/check/global_init.cpp @@ -51,6 +51,7 @@ auto GlobalInit::Finalize() -> void { .first_owning_decl_id = SemIR::InstId::None}, {.call_param_patterns_id = SemIR::InstBlockId::Empty, .call_params_id = SemIR::InstBlockId::Empty, + .call_param_default_values_id = SemIR::InstBlockId::None, .call_param_ranges = SemIR::Function::CallParamIndexRanges::Empty, .return_type_inst_id = SemIR::TypeInstId::None, .return_form_inst_id = SemIR::InstId::None, diff --git a/toolchain/check/handle_function.cpp b/toolchain/check/handle_function.cpp index 9c09438d7265..d7d3584b62e5 100644 --- a/toolchain/check/handle_function.cpp +++ b/toolchain/check/handle_function.cpp @@ -436,19 +436,22 @@ static auto BuildFunctionDecl(Context& context, // Build the function entity. This will be merged into an existing function if // there is one, or otherwise added to the function store. - auto function_info = - SemIR::Function{name_context.MakeEntityWithParamsBase( - name, decl_id, is_extern, introducer.extern_library), - {.call_param_patterns_id = name.call_param_patterns_id, - .call_params_id = name.call_params_id, - .call_param_ranges = name.param_ranges, - .return_type_inst_id = return_type_inst_id, - .return_form_inst_id = return_form_inst_id, - .return_pattern_id = return_pattern_id, - .virtual_modifier = virtual_modifier, - .evaluation_mode = evaluation_mode, - .interface_modifier = interface_modifier, - .self_param_id = self_param_id}}; + auto function_info = SemIR::Function{ + name_context.MakeEntityWithParamsBase(name, decl_id, is_extern, + introducer.extern_library), + { + .call_param_patterns_id = name.call_param_patterns_id, + .call_params_id = name.call_params_id, + .call_param_default_values_id = name.call_param_default_values_id, + .call_param_ranges = name.param_ranges, + .return_type_inst_id = return_type_inst_id, + .return_form_inst_id = return_form_inst_id, + .return_pattern_id = return_pattern_id, + .virtual_modifier = virtual_modifier, + .evaluation_mode = evaluation_mode, + .interface_modifier = interface_modifier, + .self_param_id = self_param_id, + }}; if (is_definition) { function_info.definition_id = decl_id; } @@ -515,7 +518,6 @@ static auto CheckUnusedBindingsInPattern(Context& context, SemIR::InstId pattern_id) -> void { llvm::SmallVector work_list; work_list.push_back(pattern_id); - while (!work_list.empty()) { auto current_id = work_list.pop_back_val(); auto inst = context.insts().Get(current_id); @@ -549,6 +551,10 @@ static auto CheckUnusedBindingsInPattern(Context& context, } break; } + case CARBON_KIND(SemIR::DefaultValuePattern default_value_pattern): { + work_list.push_back(default_value_pattern.subpattern_id); + break; + } default: break; } @@ -572,10 +578,143 @@ 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 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 and limit diagnostic spam. + llvm::SmallVector patterns_missing_defaults; + }; + + llvm::SmallVector level_state_stack; + level_state_stack.push_back({}); + for (auto subpattern_id : + llvm::reverse(context.inst_blocks().Get(function.param_patterns_id))) { + level_state_stack.back().subpattern_ids.push_back(subpattern_id); + } + + while (!level_state_stack.empty()) { + auto& state = level_state_stack.back(); + while (!state.subpattern_ids.empty() || !state.pattern_work_list.empty() || + state.current_id.has_value()) { + if (!state.current_id.has_value()) { + state.pattern_work_list.push_back(state.subpattern_ids.pop_back_val()); + state.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.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(); + for (auto element_id : llvm::reverse(elements)) { + state.subpattern_ids.push_back(element_id); + } + } + break; + } + default: + break; + } + } + // Finished processing this subpattern, detect a missing default if + // required. + if (state.pattern_has_default && + !state.first_pattern_with_default.has_value()) { + state.first_pattern_with_default = state.current_id; + } else if (!state.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 diag = context.emitter().Build(inst_ref.consume_front(), + RequiredPatternDefaultValueMissing); + diag.Note(state.first_pattern_with_default, + RequiredPatternDefaultValueFirstDefault); + for (auto inst_id : inst_ref) { + diag.Note(inst_id, RequiredPatternDefaultValueMissingAdditional); + } + diag.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/handle_impl.cpp b/toolchain/check/handle_impl.cpp index 1f98b5398cb3..d668cc3578dd 100644 --- a/toolchain/check/handle_impl.cpp +++ b/toolchain/check/handle_impl.cpp @@ -200,6 +200,7 @@ static auto PopImplIntroducerAndParamsAsNameComponent( .param_patterns_id = SemIR::InstBlockId::None, .call_param_patterns_id = SemIR::InstBlockId::None, .call_params_id = SemIR::InstBlockId::None, + .call_param_default_values_id = SemIR::InstBlockId::None, .param_ranges = SemIR::Function::CallParamIndexRanges::Empty, .pattern_block_id = pattern_block_id}; } diff --git a/toolchain/check/handle_pattern_list.cpp b/toolchain/check/handle_pattern_list.cpp index e2d16117fc30..ab9b3f008c8b 100644 --- a/toolchain/check/handle_pattern_list.cpp +++ b/toolchain/check/handle_pattern_list.cpp @@ -4,6 +4,7 @@ #include "toolchain/check/class.h" #include "toolchain/check/context.h" +#include "toolchain/check/eval.h" #include "toolchain/check/handle.h" #include "toolchain/check/inst.h" #include "toolchain/check/pattern.h" @@ -158,7 +159,53 @@ auto HandleParseNode(Context& context, Parse::DefaultValueUnspecifiedId node_id) auto HandleParseNode(Context& context, Parse::DefaultValuePatternId node_id) -> bool { - return context.TODO(node_id, "pattern default values"); + // On entry, the top of the node stack should have an expression for the + // default value. We evaluate it to get a constant. + auto [expr_node_id, expr_inst_id] = context.node_stack().PopExprWithNodeId(); + + // Ensure we are in an explicit parameter list, otherwise issue a diagnostic. + auto full_pattern_kind = context.full_pattern_stack().CurrentKind(); + if (full_pattern_kind != FullPatternStack::Kind::ExplicitParamList) { + CARBON_DIAGNOSTIC(PatternDefaultValueNotInParameterList, Error, + "default values are only supported in parameter lists"); + context.emitter().Emit(LocIdForDiagnostics(expr_node_id), + PatternDefaultValueNotInParameterList); + return false; + } + + auto expr_const_id = TryEvalInst(context, expr_inst_id); + if (expr_const_id == SemIR::ConstantId::NotConstant) { + CARBON_DIAGNOSTIC(PatternDefaultValueNotConstant, Error, + "default value for pattern must be constant"); + context.emitter().Emit(LocIdForDiagnostics(expr_node_id), + PatternDefaultValueNotConstant); + return false; + } + + // Look up the instruction associated with the evaluated constant. + auto constant_inst_id = context.constant_values().GetInstId(expr_const_id); + CARBON_CHECK(constant_inst_id != SemIR::InstId::None); + + // Add the value to the default values array in the full pattern stack, for + // recovery later in the NameComponent. + auto default_value_id = + context.full_pattern_stack().AddDefaultValue(constant_inst_id); + + // Next on the node stack should be the pattern for which this default was + // specified. We pop that so we can issue the DefaultValuePattern in its + // place. + auto pattern_inst_id = context.node_stack().PopPattern(); + + // The default value pattern should have the same type as the subpattern. + auto pattern_type_id = context.insts().Get(pattern_inst_id).type_id(); + auto default_value_inst_id = AddInst( + context, node_id, + {.type_id = pattern_type_id, + .subpattern_id = pattern_inst_id, + .default_value_id = default_value_id}); + context.node_stack().Push(node_id, default_value_inst_id); + + return true; } } // namespace Carbon::Check diff --git a/toolchain/check/import_ref.cpp b/toolchain/check/import_ref.cpp index 9c3a3a526fe7..ef99f8eaa885 100644 --- a/toolchain/check/import_ref.cpp +++ b/toolchain/check/import_ref.cpp @@ -2386,6 +2386,7 @@ static auto ImportFunctionDecl(ImportContext& context, {GetIncompleteLocalEntityBase(context, function_decl_id, import_function), {.call_param_patterns_id = SemIR::InstBlockId::None, .call_params_id = SemIR::InstBlockId::None, + .call_param_default_values_id = SemIR::InstBlockId::None, .call_param_ranges = import_function.call_param_ranges, .return_type_inst_id = SemIR::TypeInstId::None, .return_form_inst_id = SemIR::InstId::None, diff --git a/toolchain/check/name_component.cpp b/toolchain/check/name_component.cpp index 2ec42b60cc60..634d72772e42 100644 --- a/toolchain/check/name_component.cpp +++ b/toolchain/check/name_component.cpp @@ -13,6 +13,7 @@ auto PopNameComponent(Context& context, SemIR::InstId return_pattern_id) -> NameComponent { Parse::NodeId first_param_node_id = Parse::NoneNodeId(); Parse::NodeId last_param_node_id = Parse::NoneNodeId(); + auto call_param_default_values_id = SemIR::InstBlockId::None; // Explicit params. auto [params_node_id, param_patterns_id] = @@ -23,6 +24,10 @@ auto PopNameComponent(Context& context, SemIR::InstId return_pattern_id) context.node_stack() .PopForSoloNodeId(); last_param_node_id = params_node_id; + if (!context.full_pattern_stack().GetDefaultValues().empty()) { + call_param_default_values_id = context.inst_blocks().Add( + context.full_pattern_stack().GetDefaultValues()); + } } else { param_patterns_id = SemIR::InstBlockId::None; } @@ -74,6 +79,7 @@ auto PopNameComponent(Context& context, SemIR::InstId return_pattern_id) .param_patterns_id = *param_patterns_id, .call_param_patterns_id = call_param_patterns_id, .call_params_id = call_params_id, + .call_param_default_values_id = call_param_default_values_id, .param_ranges = param_ranges, .pattern_block_id = pattern_block_id, }; diff --git a/toolchain/check/name_component.h b/toolchain/check/name_component.h index c822e957ffdb..745bd70b5434 100644 --- a/toolchain/check/name_component.h +++ b/toolchain/check/name_component.h @@ -40,6 +40,8 @@ struct NameComponent { // SemIR::EntityWithParamsBase). SemIR::InstBlockId call_param_patterns_id; SemIR::InstBlockId call_params_id; + // The pattern default values as extracted from the parameter list. + SemIR::InstBlockId call_param_default_values_id; SemIR::Function::CallParamIndexRanges param_ranges; // The pattern block. diff --git a/toolchain/check/pattern_match.cpp b/toolchain/check/pattern_match.cpp index 79ca2941d667..169f00e7fba3 100644 --- a/toolchain/check/pattern_match.cpp +++ b/toolchain/check/pattern_match.cpp @@ -203,6 +203,8 @@ class MatchContext { SemIR::InstId scrutinee_id, WorkItem entry) -> void; auto DoPreWork(State state, SemIR::ImportRefLoaded import_ref, SemIR::InstId scrutinee_id, WorkItem entry) -> void; + auto DoPreWork(State state, SemIR::DefaultValuePattern default_value_pattern, + SemIR::InstId scrutinee_id, WorkItem entry) -> void; // Do the post-work for `entry`. `entry.work` must be a `PostWork`, and // the pattern argument must be the value of `entry.pattern_id` in `context_`. @@ -222,6 +224,8 @@ class MatchContext { WorkItem entry) -> void; auto DoPostWork(State state, SemIR::ImportRefLoaded import_ref, WorkItem entry) -> void; + auto DoPostWork(State state, SemIR::DefaultValuePattern default_value_pattern, + WorkItem entry) -> void; // Performs the core logic of matching a variable pattern whose scrutinee // type is `scrutinee_type_id`, but returns the scrutinee that its subpattern @@ -922,6 +926,70 @@ auto MatchContext::DoPostWork(State /*state*/, specific_id_stack_.pop_back(); } +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"); + } + // 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, + .work = PreWork{.scrutinee_id = scrutinee_id}, + .allow_unmarked_ref = entry.allow_unmarked_ref}); +} + +auto MatchContext::DoPostWork(State state, + SemIR::DefaultValuePattern default_value_pattern, + WorkItem entry) -> void { + if (!std::holds_alternative(state)) { + CARBON_FATAL("Unhandled state kind in DefaultValuePattern post-work"); + } + // Extract the type of the parameter from the parameter instruction. + auto param_inst_id = results_stack_.PeekArray().back(); + auto param_type_id = context_.insts().Get(param_inst_id).type_id(); + + auto default_value_inst_id = + context_.full_pattern_stack() + .GetDefaultValues()[default_value_pattern.default_value_id.index]; + // If a constant was specified, we should be able to convert it into the + // type of the parameter. + if (default_value_inst_id != SemIR::InstId::None) { + // We should be able to convert the supplied constant into the type of + // the parameter. + auto converted_id = TryConvertToValueOfType( + context_, context_.insts().GetCanonicalLocId(default_value_inst_id), + default_value_inst_id, param_type_id); + if (converted_id == SemIR::ErrorInst::InstId) { + CARBON_DIAGNOSTIC( + PatternDefaultValueTypeMismatch, Error, + "default value expression type {0} doesn't match pattern type {1}", + TypeOfInstId, TypeOfInstId); + + // TODO: should be able to provide precise locations for both default + // value expression and the type of the pattern, but we can't because + // they are both constants. + context_.emitter().Emit( + LocIdForDiagnostics( + context_.insts().GetCanonicalLocId(entry.pattern_id)), + PatternDefaultValueTypeMismatch, default_value_inst_id, + param_inst_id); + } + } + results_stack_.PopArray(); + + // If something at a higher level in the stack needed these results, bubble + // up the parameter instruction we popped off our own results array. + if (need_subpattern_results()) { + results_stack_.AppendToTop(param_inst_id); + } +} + auto MatchContext::Dispatch(State state, WorkItem entry) -> void { if (entry.pattern_id == SemIR::ErrorInst::InstId) { if (need_subpattern_results()) { @@ -980,6 +1048,10 @@ auto MatchContext::Dispatch(State state, WorkItem entry) -> void { DoPreWork(state, import_ref, work.scrutinee_id, entry); break; } + case CARBON_KIND(SemIR::DefaultValuePattern default_value_pattern): { + DoPreWork(state, default_value_pattern, work.scrutinee_id, entry); + break; + } default: { CARBON_FATAL("Inst kind not handled: {0}", pattern.kind()); } @@ -1020,6 +1092,10 @@ auto MatchContext::Dispatch(State state, WorkItem entry) -> void { DoPostWork(state, import_ref, entry); break; } + case CARBON_KIND(SemIR::DefaultValuePattern default_value_pattern): { + DoPostWork(state, default_value_pattern, entry); + break; + } default: { CARBON_FATAL("Inst kind not handled: {0}", pattern.kind()); } diff --git a/toolchain/check/testdata/function/declaration/default_values.carbon b/toolchain/check/testdata/function/declaration/default_values.carbon new file mode 100644 index 000000000000..8ec9f4a85605 --- /dev/null +++ b/toolchain/check/testdata/function/declaration/default_values.carbon @@ -0,0 +1,106 @@ +// 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/primitives.carbon +// EXTRA-ARGS: --dump-sem-ir-ranges=only +// +// AUTOUPDATE +// TIP: To test this file alone, run: +// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/check/testdata/function/declaration/default_values.carbon +// TIP: To dump output, run: +// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/check/testdata/function/declaration/default_values.carbon + +// --- fail_value_not_constant.carbon + +library "[[@TEST_NAME]]"; + +// CHECK:STDERR: fail_value_not_constant.carbon:[[@LINE+4]]:23: error: default value for pattern must be constant [PatternDefaultValueNotConstant] +// CHECK:STDERR: fn H(x: i32, y: i32 = x); +// CHECK:STDERR: ^ +// CHECK:STDERR: +fn H(x: i32, y: i32 = x); + +// --- fail_type_mismatch.carbon + +library "[[@TEST_NAME]]"; + +// CHECK:STDERR: fail_type_mismatch.carbon:[[@LINE+4]]:6: error: default value expression type `str` doesn't match pattern type `i32` [PatternDefaultValueTypeMismatch] +// CHECK:STDERR: fn K(x: i32 = "bazz"); +// CHECK:STDERR: ^~~~~~~~~~~~~~~ +// CHECK:STDERR: +fn K(x: i32 = "bazz"); + +// --- fail_pattern_defaults_not_in_parameter_list.carbon + +library "[[@TEST_NAME]]"; + +// CHECK:STDERR: fail_pattern_defaults_not_in_parameter_list.carbon:[[@LINE+4]]:23: error: default values are only supported in parameter lists [PatternDefaultValueNotInParameterList] +// CHECK:STDERR: let (y: i32, x: i32 = 0) = (1, 2); +// CHECK:STDERR: ^ +// CHECK:STDERR: +let (y: i32, x: i32 = 0) = (1, 2); + +// --- fail_required_default_values_missing.carbon + +library "[[@TEST_NAME]]"; + +// CHECK:STDERR: fail_required_default_values_missing.carbon:[[@LINE+17]]:39: error: this pattern is missing a required default value. [RequiredPatternDefaultValueMissing] +// CHECK:STDERR: fn Z(v: i32, x: i32 = 3, (y: i32 = 2, z: i32), w: i32 = 4, k: i32); +// CHECK:STDERR: ^~~~~~ +// CHECK:STDERR: fail_required_default_values_missing.carbon:[[@LINE+14]]:27: note: all patterns to the right of this first pattern with a default value must also specify a default value. [RequiredPatternDefaultValueFirstDefault] +// CHECK:STDERR: fn Z(v: i32, x: i32 = 3, (y: i32 = 2, z: i32), w: i32 = 4, k: i32); +// CHECK:STDERR: ^~~~~~ +// CHECK:STDERR: +// CHECK:STDERR: fail_required_default_values_missing.carbon:[[@LINE+10]]:26: error: this pattern is missing a required default value. [RequiredPatternDefaultValueMissing] +// CHECK:STDERR: fn Z(v: i32, x: i32 = 3, (y: i32 = 2, z: i32), w: i32 = 4, k: i32); +// CHECK:STDERR: ^~~~~~~~~~~~~~~~~~~~ +// CHECK:STDERR: fail_required_default_values_missing.carbon:[[@LINE+7]]:14: note: all patterns to the right of this first pattern with a default value must also specify a default value. [RequiredPatternDefaultValueFirstDefault] +// CHECK:STDERR: fn Z(v: i32, x: i32 = 3, (y: i32 = 2, z: i32), w: i32 = 4, k: i32); +// CHECK:STDERR: ^~~~~~ +// CHECK:STDERR: fail_required_default_values_missing.carbon:[[@LINE+4]]:60: note: this pattern is also missing a required default value. [RequiredPatternDefaultValueMissingAdditional] +// CHECK:STDERR: fn Z(v: i32, x: i32 = 3, (y: i32 = 2, z: i32), w: i32 = 4, k: i32); +// CHECK:STDERR: ^~~~~~ +// CHECK:STDERR: +fn Z(v: i32, x: i32 = 3, (y: i32 = 2, z: i32), w: i32 = 4, k: i32); + +// --- basic.carbon + +library "[[@TEST_NAME]]"; + +//@dump-sem-ir-begin +fn F(x: i32 = 0); +//@dump-sem-ir-end + +// CHECK:STDOUT: --- basic.carbon +// CHECK:STDOUT: +// CHECK:STDOUT: constants { +// CHECK:STDOUT: %int_32: Core.IntLiteral = int_value 32 [concrete] +// CHECK:STDOUT: %i32: type = class_type @Int, @Int(%int_32) [concrete] +// CHECK:STDOUT: %pattern_type.6b6: type = pattern_type %i32 [concrete] +// CHECK:STDOUT: %x.param_patt: %pattern_type.6b6 = value_param_pattern [concrete] +// CHECK:STDOUT: %x.patt: %pattern_type.6b6 = wrapper_binding_pattern x, %x.param_patt [concrete] +// CHECK:STDOUT: %int_0.5c6: Core.IntLiteral = int_value 0 [concrete] +// CHECK:STDOUT: %.e53: %pattern_type.6b6 = default_value_pattern %x.patt, index: 0 [concrete] +// CHECK:STDOUT: %F.type: type = fn_type @F [concrete] +// CHECK:STDOUT: %F: %F.type = struct_value () [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.6b6 = value_param_pattern [concrete = constants.%x.param_patt] +// CHECK:STDOUT: %x.patt: %pattern_type.6b6 = wrapper_binding_pattern x, %x.param_patt [concrete = constants.%x.patt] +// CHECK:STDOUT: %.loc5: %pattern_type.6b6 = default_value_pattern %x.patt, index: 0 [concrete = constants.%.e53] +// CHECK:STDOUT: } { +// CHECK:STDOUT: %int_0: Core.IntLiteral = int_value 0 [concrete = constants.%int_0.5c6] +// CHECK:STDOUT: %x.param: %i32 = value_param call_param0 +// CHECK:STDOUT: %i32: type = type_literal constants.%i32 [concrete = constants.%i32] +// CHECK:STDOUT: %x: %i32 = wrapper_binding x, %x.param +// CHECK:STDOUT: +// CHECK:STDOUT: } +// CHECK:STDOUT: } +// CHECK:STDOUT: +// CHECK:STDOUT: fn @F(%x.param: %i32) default_values: { +// CHECK:STDOUT: constants.%int_0.5c6: Core.IntLiteral = int_value 0 [concrete] +// CHECK:STDOUT: }; +// CHECK:STDOUT: diff --git a/toolchain/check/thunk.cpp b/toolchain/check/thunk.cpp index a6c1b786f49d..4232926bad4d 100644 --- a/toolchain/check/thunk.cpp +++ b/toolchain/check/thunk.cpp @@ -246,6 +246,7 @@ static auto CloneFunctionDecl(Context& context, SemIR::LocId loc_id, { .call_param_patterns_id = match_results.call_param_patterns_id, .call_params_id = match_results.call_params_id, + .call_param_default_values_id = SemIR::InstBlockId::None, .call_param_ranges = match_results.param_ranges, .return_type_inst_id = return_type_inst_id, .return_form_inst_id = return_form_inst_id, diff --git a/toolchain/diagnostics/kind.def b/toolchain/diagnostics/kind.def index 7b640c503616..7eb31dfd3ced 100644 --- a/toolchain/diagnostics/kind.def +++ b/toolchain/diagnostics/kind.def @@ -612,6 +612,12 @@ CARBON_DIAGNOSTIC_KIND(FacetTypeRequiredForTypeAndOperator) CARBON_DIAGNOSTIC_KIND(GenericMissingExplicitParameters) // Pattern matching diagnostics. +CARBON_DIAGNOSTIC_KIND(RequiredPatternDefaultValueMissing) +CARBON_DIAGNOSTIC_KIND(RequiredPatternDefaultValueFirstDefault) +CARBON_DIAGNOSTIC_KIND(RequiredPatternDefaultValueMissingAdditional) +CARBON_DIAGNOSTIC_KIND(PatternDefaultValueNotConstant) +CARBON_DIAGNOSTIC_KIND(PatternDefaultValueNotInParameterList) +CARBON_DIAGNOSTIC_KIND(PatternDefaultValueTypeMismatch) CARBON_DIAGNOSTIC_KIND(TuplePatternSizeDoesntMatchLiteral) // Unused diagnostics. diff --git a/toolchain/sem_ir/formatter.cpp b/toolchain/sem_ir/formatter.cpp index f19f917f7ea4..4c8ab01601ba 100644 --- a/toolchain/sem_ir/formatter.cpp +++ b/toolchain/sem_ir/formatter.cpp @@ -597,6 +597,20 @@ auto Formatter::FormatFunction(FunctionId id, const Function& fn) -> void { out() << "]"; } + if (fn.call_param_default_values_id.has_value()) { + out() << " default_values:"; + // The default values are encoded as instructions referring to constants, + // and as such have no location, and so are normally elided when + // use_dump_sem_ir_ranges_ is true. However, if the function containing + // these default values is to be printed, it should also include these + // defaults, so we temporarily disable this flag to force the printing of + // the contents of this block. + auto format_mask = use_dump_sem_ir_ranges_; + use_dump_sem_ir_ranges_ = false; + FormatTrailingBlock(fn.call_param_default_values_id); + use_dump_sem_ir_ranges_ = format_mask; + } + if (!fn.body_block_ids.empty()) { out() << ' '; OpenBrace(); @@ -1637,6 +1651,10 @@ auto Formatter::FormatArg(DeclaredFacetTypeId id) -> void { out() << ">"; } +auto Formatter::FormatArg(DefaultValueId id) -> void { + out() << "index: " << id.index; +} + auto Formatter::FormatArg(FieldId id) -> void { const auto& field = sem_ir_->fields().Get(id); out() << field.index; diff --git a/toolchain/sem_ir/formatter.h b/toolchain/sem_ir/formatter.h index c1c7c73cdb33..b22514ecf062 100644 --- a/toolchain/sem_ir/formatter.h +++ b/toolchain/sem_ir/formatter.h @@ -273,6 +273,7 @@ class Formatter { auto FormatArg(CharId c) -> void { out() << c; } auto FormatArg(EntityNameId id) -> void; auto FormatArg(DeclaredFacetTypeId id) -> void; + auto FormatArg(DefaultValueId id) -> void; auto FormatArg(FieldId id) -> void; auto FormatArg(IntKind k) -> void { k.Print(out()); } auto FormatArg(FloatKind k) -> void { k.Print(out()); } diff --git a/toolchain/sem_ir/function.h b/toolchain/sem_ir/function.h index f5d3e886e975..814116be674f 100644 --- a/toolchain/sem_ir/function.h +++ b/toolchain/sem_ir/function.h @@ -82,6 +82,9 @@ struct FunctionFields { // because it is relevant only for a function definition. InstBlockId call_params_id; + // Instructions representing the constant default values for parameters. + InstBlockId call_param_default_values_id; + // The index ranges within the `Call` parameters that correspond to the // implicit parameters, explicit parameters, and return. // @@ -231,6 +234,9 @@ struct Function : public EntityWithParamsBase, if (call_params_id.has_value()) { out << ", call_params_id: " << call_params_id; } + if (call_param_default_values_id.has_value()) { + out << ", call_param_default_values_id: " << call_param_default_values_id; + } if (return_type_inst_id.has_value()) { out << ", return_type_inst_id: " << return_type_inst_id; } @@ -315,7 +321,8 @@ struct Function : public EntityWithParamsBase, -> InstId; // When merging a declaration and definition, prefer things which would point - // at the definition for diagnostics. + // at the definition for diagnostics. Note that merging parameter default + // values needs more context, so doesn't happen here. auto MergeDefinition(const Function& definition) -> void { EntityWithParamsBase::MergeBaseDefinition(definition); call_param_patterns_id = definition.call_param_patterns_id; diff --git a/toolchain/sem_ir/id_kind.h b/toolchain/sem_ir/id_kind.h index e742db71b512..2f2079573893 100644 --- a/toolchain/sem_ir/id_kind.h +++ b/toolchain/sem_ir/id_kind.h @@ -45,6 +45,7 @@ using IdKind = TypeEnum< CustomLayoutId, DeclaredFacetTypeId, DeclInstBlockId, + DefaultValueId, DestInstId, ElementIndex, EntityNameId, diff --git a/toolchain/sem_ir/ids.h b/toolchain/sem_ir/ids.h index 1a8972c70535..e0d93e39c7b8 100644 --- a/toolchain/sem_ir/ids.h +++ b/toolchain/sem_ir/ids.h @@ -116,6 +116,14 @@ class AbsoluteInstId : public InstId { using InstId::InstId; }; +// An id representing the index of the default value constant instruction in +// a default values instruction block. +class DefaultValueId : public IdBase { + public: + static constexpr llvm::StringLiteral Label = "default_value_id"; + using IdBase::IdBase; +}; + // An ID of an instruction that is used as the destination of an initializing // expression. This should only be used as the type of a field within a typed // instruction class. diff --git a/toolchain/sem_ir/inst_fingerprinter.cpp b/toolchain/sem_ir/inst_fingerprinter.cpp index 19f95f79f683..e9c2e590b5c4 100644 --- a/toolchain/sem_ir/inst_fingerprinter.cpp +++ b/toolchain/sem_ir/inst_fingerprinter.cpp @@ -636,8 +636,9 @@ struct Worklist { } template - requires(SameAsOneOf) + requires( + SameAsOneOf) auto Add(T arg) -> void { // Index-like ID: just include the value directly. AddInteger(arg.index); diff --git a/toolchain/sem_ir/inst_kind.def b/toolchain/sem_ir/inst_kind.def index 2bf171ad7f5e..5648b7f741b1 100644 --- a/toolchain/sem_ir/inst_kind.def +++ b/toolchain/sem_ir/inst_kind.def @@ -65,6 +65,7 @@ CARBON_SEM_IR_INST_KIND(CppOverloadSetValue) CARBON_SEM_IR_INST_KIND(CppTemplateNameType) CARBON_SEM_IR_INST_KIND(CustomLayoutType) CARBON_SEM_IR_INST_KIND(CustomWitness) +CARBON_SEM_IR_INST_KIND(DefaultValuePattern) CARBON_SEM_IR_INST_KIND(Deref) CARBON_SEM_IR_INST_KIND(EnclosingClassAccess) CARBON_SEM_IR_INST_KIND(ErrorInst) diff --git a/toolchain/sem_ir/typed_insts.h b/toolchain/sem_ir/typed_insts.h index 171cdd5a68d5..64065e8d58eb 100644 --- a/toolchain/sem_ir/typed_insts.h +++ b/toolchain/sem_ir/typed_insts.h @@ -726,6 +726,21 @@ struct CustomWitness { SpecificInterfaceId query_specific_interface_id; }; +// Describes a constant default value for a pattern, which may be used if that +// pattern is absent in a scrutinee. +struct DefaultValuePattern { + static constexpr auto Kind = + InstKind::DefaultValuePattern.Define( + {.ir_name = "default_value_pattern", + .expr_category = ExprCategory::Pattern, + .constant_kind = InstConstantKind::Always, + .is_lowered = false}); + + TypeId type_id; + InstId subpattern_id; + DefaultValueId default_value_id; +}; + // The `*` dereference operator, as in `*pointer`. struct Deref { static constexpr auto Kind = InstKind::Deref.Define(