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
+38 -19
View File
@@ -17,14 +17,13 @@
namespace Carbon::Check {
class Context;
// A stack of lexical and semantic scopes that we are currently performing
// checking within.
class ScopeStack {
public:
explicit ScopeStack(const SemIR::File* sem_ir)
: sem_ir_(sem_ir),
lexical_lookup_(sem_ir->identifiers()),
full_pattern_stack_(&lexical_lookup_) {}
explicit ScopeStack(Context& context);
// A scope in which `break` and `continue` can be used.
struct BreakContinueScope {
@@ -66,17 +65,18 @@ class ScopeStack {
auto PushForFunctionBody(SemIR::InstId scope_inst_id) -> void;
// Pops the top scope from scope_stack_. Removes names from lexical_lookup_.
auto Pop() -> void;
// If `check_unused` is set, checks and emits diagnostics for unused names.
auto Pop(bool check_unused = false) -> void;
// Pops the top scope from scope_stack_ if it contains no names.
auto PopIfEmpty() -> void {
auto PopIfEmpty(bool check_unused = false) -> void {
if (scope_stack_.back().num_names == 0) {
Pop();
Pop(check_unused);
}
}
// Pops scopes until we return to the specified scope index.
auto PopTo(ScopeIndex index) -> void;
auto PopTo(ScopeIndex index, bool check_unused = false) -> void;
// Returns the scope index associated with the current scope.
auto PeekIndex() const -> ScopeIndex { return Peek().index; }
@@ -111,7 +111,7 @@ class ScopeStack {
if (!inst_id.has_value()) {
return std::nullopt;
}
return sem_ir_->insts().TryGetAs<InstT>(inst_id);
return sem_ir().insts().TryGetAs<InstT>(inst_id);
}
// Returns the current scope, assuming it is of the specified kind.
@@ -121,13 +121,14 @@ class ScopeStack {
auto GetCurrentScopeAs() -> InstT {
auto inst_id = PeekInstId();
CARBON_CHECK(inst_id.has_value());
return sem_ir_->insts().GetAs<InstT>(inst_id);
return sem_ir().insts().GetAs<InstT>(inst_id);
}
// If there is no `returned var` in scope, sets the given instruction to be
// the current `returned var` and returns an `None`. If there
// is already a `returned var`, returns it instead.
auto SetReturnedVarOrGetExisting(SemIR::InstId inst_id) -> SemIR::InstId;
auto SetReturnedVarOrGetExisting(SemIR::InstId inst_id, SemIR::NameId name_id)
-> SemIR::InstId;
// Returns the `returned var` instruction that's currently in scope, or `None`
// if there isn't one.
@@ -144,23 +145,29 @@ class ScopeStack {
// Looks up the name `name_id` in the current scope and enclosing scopes, but
// do not look past `scope_index`. Returns the existing lookup result, if any.
// If `use_loc_id` is specified, the name is marked as used at that location.
auto LookupInLexicalScopesWithin(SemIR::NameId name_id,
ScopeIndex scope_index) -> SemIR::InstId;
ScopeIndex scope_index,
SemIR::LocId use_loc_id, bool is_reachable)
-> SemIR::InstId;
// Looks up the name `name_id` in the current scope and related lexical
// scopes. Returns the innermost lexical lookup result, if any, along with a
// list of non-lexical scopes in which lookup should also be performed,
// ordered from outermost to innermost.
auto LookupInLexicalScopes(SemIR::NameId name_id)
// ordered from outermost to innermost. If `use_loc_id` is specified, the
// name is marked as used at that location.
auto LookupInLexicalScopes(SemIR::NameId name_id, SemIR::LocId use_loc_id,
bool is_reachable)
-> std::pair<SemIR::InstId, llvm::ArrayRef<NonLexicalScope>>;
// Looks up the name `name_id` in the current scope, or in `scope_index` if
// specified. Returns the existing instruction if the name is already declared
// in that scope or any unfinished scope within it, and otherwise adds the
// name with the value `target_id` and returns `None`.
// name with the value `target_id` and returns `None`. `is_decl_reachable`
// indicates whether the name was declared in a reachable position.
auto LookupOrAddName(SemIR::NameId name_id, SemIR::InstId target_id,
ScopeIndex scope_index = ScopeIndex::None)
-> SemIR::InstId;
ScopeIndex scope_index = ScopeIndex::None,
bool is_decl_reachable = true) -> SemIR::InstId;
// Prepares to add a compile-time binding in the current scope, and returns
// its index. The added binding must then be pushed using
@@ -200,6 +207,10 @@ class ScopeStack {
auto full_pattern_stack() -> FullPatternStack& { return full_pattern_stack_; }
private:
auto sem_ir() const -> const SemIR::File&;
auto lexical_lookup() -> LexicalLookup& { return lexical_lookup_; }
// An entry in scope_stack_.
struct ScopeStackEntry : public MoveOnly<ScopeStackEntry> {
auto is_lexical_scope() const -> bool { return !scope_id.has_value(); }
@@ -286,6 +297,10 @@ class ScopeStack {
}
}
// Marks the name `name_id` as used at the given location.
auto MarkUsed(SemIR::NameId name_id, SemIR::LocId loc_id, bool is_reachable)
-> void;
// Checks that the provided scope's `next_compile_time_bind_index` matches the
// full size of the current `compile_time_binding_stack_`. The values should
// always match, and this is used to validate the correspondence during
@@ -293,8 +308,8 @@ class ScopeStack {
auto VerifyNextCompileTimeBindIndex(llvm::StringLiteral label,
const ScopeStackEntry& scope) -> void;
// The current file.
const SemIR::File* sem_ir_;
// Context, used only for checks and emitting diagnostics.
Context* context_;
// A stack of scopes from which we can `return`.
llvm::SmallVector<ReturnScope> return_scope_stack_;
@@ -344,6 +359,10 @@ struct ScopeStack::SuspendedScope : public MoveOnly<SuspendedScope> {
uint32_t index;
// The instruction within the scope.
SemIR::InstId inst_id;
// Whether the name was declared in a reachable position.
bool is_decl_reachable;
// The location of the first use of the name, if any.
SemIR::LocId use_loc_id;
};
// The suspended scope stack entry.