|
18 | 18 |
|
19 | 19 | namespace Carbon::Check { |
20 | 20 |
|
21 | | -CARBON_DIAGNOSTIC(UnusedVariable, Warning, "variable `{0}` is unused", |
22 | | - std::string); |
23 | | - |
24 | 21 | // Represents a single fact with two IDs. |
25 | 22 | // The meaning of id1 and id2 depends on the FactType. |
26 | 23 | // Using int32_t to store the raw index values of the various Id types. |
@@ -263,36 +260,79 @@ static auto BuildDataflowFacts(const SemIR::File& sem_ir, |
263 | 260 | static auto CheckUnusedVariables(Context& context, const DataflowFacts& facts) |
264 | 261 | -> void { |
265 | 262 | auto& sem_ir = context.sem_ir(); |
266 | | - // Collect all used variable IDs (EntityNameId indices). |
267 | | - Set<int32_t> used_vars; |
268 | | - facts.uses.ForEach([&](const Fact& use) { used_vars.Insert(use.id2); }); |
269 | 263 |
|
270 | | - // Collect unused definitions. |
| 264 | + // Collect usage locations. We track the first source-location use for each |
| 265 | + // variable. |
| 266 | + llvm::DenseMap<int32_t, SemIR::InstId> first_use; |
| 267 | + facts.uses.ForEach([&](const Fact& use) { |
| 268 | + auto [it, inserted] = first_use.insert({use.id2, SemIR::InstId(use.id1)}); |
| 269 | + if (!inserted) { |
| 270 | + // Keep the earliest instruction ID. |
| 271 | + if (use.id1 < it->second.index) { |
| 272 | + it->second = SemIR::InstId(use.id1); |
| 273 | + } |
| 274 | + } |
| 275 | + }); |
| 276 | + |
| 277 | + // Collect definitions to diagnose. |
271 | 278 | llvm::SmallVector<Fact> unused_defs; |
| 279 | + llvm::SmallVector<Fact> unused_but_used_defs; |
| 280 | + |
272 | 281 | facts.defs.ForEach([&](const Fact& def) { |
273 | 282 | auto var_id = def.id2; |
274 | | - if (!used_vars.Contains(var_id)) { |
275 | | - unused_defs.push_back(def); |
| 283 | + auto entity_name_id = SemIR::EntityNameId(var_id); |
| 284 | + const auto& entity_name = sem_ir.entity_names().Get(entity_name_id); |
| 285 | + |
| 286 | + if (first_use.find(var_id) == first_use.end()) { |
| 287 | + if (!entity_name.is_unused) { |
| 288 | + unused_defs.push_back(def); |
| 289 | + } |
| 290 | + } else { |
| 291 | + if (entity_name.is_unused) { |
| 292 | + unused_but_used_defs.push_back(def); |
| 293 | + } |
276 | 294 | } |
277 | 295 | }); |
278 | 296 |
|
279 | 297 | // Sort by instruction ID (location). |
280 | | - std::sort(unused_defs.begin(), unused_defs.end(), |
281 | | - [](const Fact& a, const Fact& b) { return a.id1 < b.id1; }); |
| 298 | + auto sort_facts = [](const Fact& a, const Fact& b) { return a.id1 < b.id1; }; |
| 299 | + std::sort(unused_defs.begin(), unused_defs.end(), sort_facts); |
| 300 | + std::sort(unused_but_used_defs.begin(), unused_but_used_defs.end(), |
| 301 | + sort_facts); |
282 | 302 |
|
283 | 303 | // Emit diagnostics. |
| 304 | + for (const auto& def : unused_but_used_defs) { |
| 305 | + auto var_id = def.id2; |
| 306 | + auto entity_name_id = SemIR::EntityNameId(var_id); |
| 307 | + const auto& entity_name = sem_ir.entity_names().Get(entity_name_id); |
| 308 | + auto name_id = entity_name.name_id; |
| 309 | + llvm::StringRef name = sem_ir.names().GetFormatted(name_id); |
| 310 | + auto inst_id = SemIR::InstId(def.id1); |
| 311 | + auto loc_id = sem_ir.insts().GetCanonicalLocId(inst_id); |
| 312 | + CARBON_DIAGNOSTIC(UnusedButUsed, Error, |
| 313 | + "variable `{0}` is marked `unused` but is used", |
| 314 | + std::string); |
| 315 | + auto diag = context.emitter().Build(LocIdForDiagnostics(loc_id), |
| 316 | + UnusedButUsed, name.str()); |
| 317 | + auto use_inst_id = first_use.find(var_id)->second; |
| 318 | + auto use_loc_id = sem_ir.insts().GetCanonicalLocId(use_inst_id); |
| 319 | + CARBON_DIAGNOSTIC(UnusedButUsedHere, Note, "usage is here"); |
| 320 | + diag.Note(LocIdForDiagnostics(use_loc_id), UnusedButUsedHere); |
| 321 | + diag.Emit(); |
| 322 | + } |
| 323 | + |
284 | 324 | for (const auto& def : unused_defs) { |
285 | 325 | auto var_id = def.id2; |
286 | 326 | auto entity_name_id = SemIR::EntityNameId(var_id); |
287 | 327 | const auto& entity_name = sem_ir.entity_names().Get(entity_name_id); |
288 | | - if (!entity_name.is_unused) { |
289 | | - auto name_id = entity_name.name_id; |
290 | | - llvm::StringRef name = sem_ir.names().GetFormatted(name_id); |
291 | | - auto inst_id = SemIR::InstId(def.id1); |
292 | | - auto loc_id = sem_ir.insts().GetCanonicalLocId(inst_id); |
293 | | - context.emitter().Emit(LocIdForDiagnostics(loc_id), UnusedVariable, |
294 | | - name.str()); |
295 | | - } |
| 328 | + auto name_id = entity_name.name_id; |
| 329 | + llvm::StringRef name = sem_ir.names().GetFormatted(name_id); |
| 330 | + auto inst_id = SemIR::InstId(def.id1); |
| 331 | + auto loc_id = sem_ir.insts().GetCanonicalLocId(inst_id); |
| 332 | + CARBON_DIAGNOSTIC(UnusedVariable, Warning, "variable `{0}` is unused", |
| 333 | + std::string); |
| 334 | + context.emitter().Emit(LocIdForDiagnostics(loc_id), UnusedVariable, |
| 335 | + name.str()); |
296 | 336 | } |
297 | 337 | } |
298 | 338 |
|
|
0 commit comments