diff --git a/scripts/diagnostics.txt b/scripts/diagnostics.txt index 0255ab854..eb4d59d15 100644 --- a/scripts/diagnostics.txt +++ b/scripts/diagnostics.txt @@ -776,10 +776,7 @@ warning bitwise-op-mismatch BitwiseOpMismatch "bitwise operation between operand warning comparison-mismatch ComparisonMismatch "comparison between operands of different types ({} and {})" warning sign-compare SignCompare "comparison of differently signed types ({} and {})" warning case-type CaseTypeMismatch "comparison of different types in '{}' statement ({} and {})" -warning case-default CaseDefault "'{}' missing 'default' label" warning case-outside-range CaseOutsideRange "'{}' item with {} bits can never match the {} bit case expression" -warning case-enum CaseEnum "enumeration {} not handled in case statement" -warning case-enum-explicit CaseEnumExplicit "enumeration {} not explicitly handled in case statement" warning bitwise-rel-precedence BitwiseRelPrecedence "'{}' has lower precedence than '{}'; '{}' will be evaluated first" note NotePrecedenceBitwiseFirst "place parentheses around the '{}' expression to evaluate it first" note NotePrecedenceSilence "place parentheses around the '{}' expression to silence this warning" @@ -872,6 +869,10 @@ error BlockingInAlwaysFF "always_ff procedures cannot have blocking timing contr error AlwaysFFEventControl "always_ff procedure must have one and only one event control" error SeqEmptyMatch "sequence must not admit an empty match" error SeqOnlyEmpty "sequence admits only empty matches" +warning case-default CaseDefault "'{}' missing 'default' label" +warning case-enum CaseEnum "enumeration {} not handled in case statement" +warning case-enum-explicit CaseEnumExplicit "enumeration {} not explicitly handled in case statement" +warning case-dup CaseDup "'{}' statement contains duplicate items for value '{}' (the second one will never be matched)" warning seq-no-match SeqNoMatch "sequence can never be matched" warning event-const EventExpressionConstant "edge expression is constant" warning empty-stmt EmptyStatement "extra ';' has no effect" @@ -1174,7 +1175,7 @@ group extra = { empty-member empty-stmt dup-import pointless-void-cast case-gen- ineffective-sign port-width-trunc constant-conversion float-bool-conv int-bool-conv useless-cast unsigned-arith-shift static-init-order static-init-value float-int-conv float-narrow bitwise-rel-precedence arith-in-shift logical-not-parentheses - consecutive-comparison case-outside-range case-enum } + consecutive-comparison case-outside-range case-enum case-dup } group pedantic = { empty-pattern implicit-net-port nonstandard-escape-code nonstandard-generate format-multibit-strength nonstandard-sys-func nonstandard-foreach nonstandard-dist isunbounded-param-arg udp-coverage } diff --git a/scripts/warning_docs.txt b/scripts/warning_docs.txt index 2078bcace..fc4723625 100644 --- a/scripts/warning_docs.txt +++ b/scripts/warning_docs.txt @@ -1561,3 +1561,20 @@ module m; end endmodule ``` + +-Wcase-dup +A case statement contains more than one item with the same value. +Case items are evaluated and selected in order and so the second +one will never be matched. +``` +module m; + int i; + initial begin + case (i) + 1, 2:; + 3, 1:; + default; + endcase + end +endmodule +``` diff --git a/source/ast/Statements.cpp b/source/ast/Statements.cpp index 6f26677ec..5a627fa7c 100644 --- a/source/ast/Statements.cpp +++ b/source/ast/Statements.cpp @@ -1201,14 +1201,21 @@ Statement& CaseStatement::fromSyntax(Compilation& compilation, const CaseStateme << LexerFacts::getTokenKindText(keyword); } - if (expr->type->isEnum()) { - SmallSet elems; - for (auto& g : result->items) { - for (auto item : g.expressions) { - if (auto cv = context.tryEval(*item)) - elems.emplace(std::move(cv)); + SmallMap elems; + for (auto& g : result->items) { + for (auto item : g.expressions) { + if (auto cv = context.tryEval(*item)) { + auto [it, inserted] = elems.emplace(std::move(cv), item); + if (!inserted) { + auto& diag = context.addDiag(diag::CaseDup, item->sourceRange); + diag << LexerFacts::getTokenKindText(keyword) << it->first; + diag.addNote(diag::NotePreviousUsage, it->second->sourceRange); + } } } + } + + if (expr->type->isEnum()) { SmallVector missing; for (auto& ev : expr->type->getCanonicalType().as().values()) { diff --git a/tests/unittests/ast/WarningTests.cpp b/tests/unittests/ast/WarningTests.cpp index b90f2a066..f6004f860 100644 --- a/tests/unittests/ast/WarningTests.cpp +++ b/tests/unittests/ast/WarningTests.cpp @@ -1181,3 +1181,32 @@ endmodule CHECK(diags[10].code == diag::CaseDefault); CHECK(diags[11].code == diag::CaseEnum); } + +TEST_CASE("Case statement dups") { + auto tree = SyntaxTree::fromText(R"( +module m; + int i; + event e; + initial begin + case (i) + 1, 2:; + 3, 1:; + default; + endcase + case (e) + null:; + null:; + default; + endcase + end +endmodule +)"); + + Compilation compilation; + compilation.addSyntaxTree(tree); + + auto& diags = compilation.getAllDiagnostics(); + REQUIRE(diags.size() == 2); + CHECK(diags[0].code == diag::CaseDup); + CHECK(diags[1].code == diag::CaseDup); +}