Implement unused pattern bindings, continued (#6518)

Implementation of unused pattern bindings #2022, continued.

Whereas previous PR #6460 took care of parsing, and PR #6479 prepared
the stage by using _ in some test cases, this PR has the the actual
implementation, using a simple dataflow analysis.

---------

Co-authored-by: Burak Emir <bqe@google.com>
Co-authored-by: jonmeow <jperkins@google.com>
This commit is contained in:
Burak Emir
2026-02-19 23:33:36 +00:00
committed by GitHub
co-authored by Burak Emir jonmeow
parent bea24a8bee
commit fdb188ccfd
366 changed files with 8453 additions and 7996 deletions
+72 -7
View File
@@ -2,6 +2,9 @@
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include <utility>
#include "toolchain/base/kind_switch.h"
#include "toolchain/check/context.h"
#include "toolchain/check/convert.h"
#include "toolchain/check/facet_type.h"
@@ -13,6 +16,7 @@
#include "toolchain/check/return.h"
#include "toolchain/check/type.h"
#include "toolchain/check/type_completion.h"
#include "toolchain/check/unused.h"
#include "toolchain/diagnostics/format_providers.h"
#include "toolchain/parse/node_ids.h"
#include "toolchain/sem_ir/ids.h"
@@ -102,7 +106,8 @@ static auto IsValidParamForIntroducer(Context& context, Parse::NodeId node_id,
// TODO: make this function shorter by factoring pieces out.
static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id,
Parse::NodeKind node_kind) -> bool {
Parse::NodeKind node_kind,
bool is_unused = false) -> bool {
// TODO: split this into smaller, more focused functions.
auto [type_node, parsed_type_id] = context.node_stack().PopExprWithNodeId();
auto [cast_type_inst_id, cast_type_id] =
@@ -134,9 +139,9 @@ static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id,
auto make_binding_pattern = [&]() -> SemIR::InstId {
// TODO: Eventually the name will need to support associations with other
// scopes, but right now we don't support qualified names here.
auto binding =
AddBindingPattern(context, name_node, name_id, cast_type_id,
type_expr_region_id, pattern_inst_kind, is_template);
auto binding = AddBindingPattern(context, name_node, name_id, cast_type_id,
type_expr_region_id, pattern_inst_kind,
is_template, is_unused);
// TODO: If `is_generic`, then `binding.bind_id is a SymbolicBinding. Subst
// the `.Self` of type `type` in the `cast_type_id` type (a `FacetType`)
@@ -279,7 +284,7 @@ static auto HandleAnyBindingPattern(Context& context, Parse::NodeId node_id,
.bind_name_id;
RegisterReturnedVar(context,
introducer.modifier_node_id(ModifierOrder::Decl),
type_node, cast_type_id, bind_id);
type_node, cast_type_id, bind_id, name_id);
}
}
context.node_stack().Push(node_id, binding_pattern_id);
@@ -328,7 +333,7 @@ auto HandleParseNode(Context& context,
Parse::CompileTimeBindingPatternId node_id) -> bool {
// Pop the `.Self` facet value name introduced by the
// CompileTimeBindingPatternStart.
context.scope_stack().Pop();
context.scope_stack().Pop(/*check_unused=*/true);
auto node_kind = Parse::NodeKind::CompileTimeBindingPattern;
const DeclIntroducerState& introducer =
@@ -449,8 +454,68 @@ auto HandleParseNode(Context& context, Parse::TemplateBindingNameId node_id)
return true;
}
// Within a pattern with an unused modifier, sets the is_unused on all
// entity names and also returns whether any names were found. The result
// is needed to emit a diagnostic when the unused modifier is
// unnecessary.
static auto MarkPatternUnused(Context& context, SemIR::InstId inst_id) -> bool {
bool found_name = false;
llvm::SmallVector<SemIR::InstId> worklist;
worklist.push_back(inst_id);
while (!worklist.empty()) {
auto current_inst_id = worklist.pop_back_val();
auto inst = context.insts().Get(current_inst_id);
CARBON_KIND_SWITCH(inst) {
case SemIR::OutParamPattern::Kind:
case SemIR::RefParamPattern::Kind:
case SemIR::ValueParamPattern::Kind:
case SemIR::VarParamPattern::Kind: {
auto param = inst.As<SemIR::AnyParamPattern>();
worklist.push_back(param.subpattern_id);
break;
}
case SemIR::RefBindingPattern::Kind:
case SemIR::SymbolicBindingPattern::Kind:
case SemIR::ValueBindingPattern::Kind: {
auto bind = inst.As<SemIR::AnyBindingPattern>();
auto& name = context.entity_names().Get(bind.entity_name_id);
name.is_unused = true;
// We treat `_` as not marking the pattern as unused for the purpose of
// deciding whether to issue a warning for `unused` on a pattern that
// doesn't contain any bindings. `_` is implicitly unused, so marking it
// `unused` is redundant but harmless.
if (name.name_id != SemIR::NameId::Underscore) {
found_name = true;
}
break;
}
case CARBON_KIND(SemIR::TuplePattern tuple): {
for (auto elem_id : context.inst_blocks().Get(tuple.elements_id)) {
worklist.push_back(elem_id);
}
break;
}
case CARBON_KIND(SemIR::VarPattern var): {
worklist.push_back(var.subpattern_id);
break;
}
default:
break;
}
}
return found_name;
}
auto HandleParseNode(Context& context, Parse::UnusedPatternId node_id) -> bool {
return context.TODO(node_id, "unused");
auto [child_node, child_inst_id] =
context.node_stack().PopPatternWithNodeId();
if (!MarkPatternUnused(context, child_inst_id)) {
CARBON_DIAGNOSTIC(UnusedPatternNoBindings, Warning,
"`unused` modifier on pattern without bindings");
context.emitter().Emit(node_id, UnusedPatternNoBindings);
}
context.node_stack().Push(node_id, child_inst_id);
return true;
}
} // namespace Carbon::Check