[clangd] Handle duplicate enum constants in PopulateSwitch tweak

If an enum has different names for the same constant, make sure only the first one declared gets added into the switch. Failing to do so results in a compiler error as 2 case labels can't represent the same value.

```
lang=c
enum Numbers{
One,
Un = One,
Two,
Deux = Two,
Three,
Trois = Three
};

// Old behaviour
switch (<Number>) {
  case One:
  case Un:
  case Two:
  case Duex:
  case Three:
  case Trois: break;
}

// New behaviour
switch (<Number>) {
  case One:
  case Two:
  case Three: break;
}
```

Reviewed By: sammccall

Differential Revision: https://reviews.llvm.org/D90555
This commit is contained in:
Nathan James 2020-11-09 12:14:51 +00:00
parent 57f87977f5
commit 5918ef8b1a
No known key found for this signature in database
GPG Key ID: CC007AFCDA90AA5F
3 changed files with 91 additions and 40 deletions

View File

@ -40,7 +40,8 @@
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Tooling/Core/Replacement.h"
#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/MapVector.h"
#include "llvm/ADT/STLExtras.h"
#include <cassert>
#include <string>
@ -57,11 +58,27 @@ class PopulateSwitch : public Tweak {
}
private:
class ExpectedCase {
public:
ExpectedCase(const EnumConstantDecl *Decl) : Data(Decl, false) {}
bool isCovered() const { return Data.getInt(); }
void setCovered(bool Val = true) { Data.setInt(Val); }
const EnumConstantDecl *getEnumConstant() const {
return Data.getPointer();
}
private:
llvm::PointerIntPair<const EnumConstantDecl *, 1, bool> Data;
};
const DeclContext *DeclCtx = nullptr;
const SwitchStmt *Switch = nullptr;
const CompoundStmt *Body = nullptr;
const EnumType *EnumT = nullptr;
const EnumDecl *EnumD = nullptr;
// Maps the Enum values to the EnumConstantDecl and a bool signifying if its
// covered in the switch.
llvm::MapVector<llvm::APSInt, ExpectedCase> ExpectedCases;
};
REGISTER_TWEAK(PopulateSwitch)
@ -112,21 +129,34 @@ bool PopulateSwitch::prepare(const Selection &Sel) {
if (!EnumD)
return false;
// We trigger if there are fewer cases than enum values (and no case covers
// multiple values). This guarantees we'll have at least one case to insert.
// We don't yet determine what the cases are, as that means evaluating
// expressions.
auto I = EnumD->enumerator_begin();
auto E = EnumD->enumerator_end();
// We trigger if there are any values in the enum that aren't covered by the
// switch.
for (const SwitchCase *CaseList = Switch->getSwitchCaseList();
CaseList && I != E; CaseList = CaseList->getNextSwitchCase(), I++) {
ASTContext &Ctx = Sel.AST->getASTContext();
unsigned EnumIntWidth = Ctx.getIntWidth(QualType(EnumT, 0));
bool EnumIsSigned = EnumT->isSignedIntegerOrEnumerationType();
auto Normalize = [&](llvm::APSInt Val) {
Val = Val.extOrTrunc(EnumIntWidth);
Val.setIsSigned(EnumIsSigned);
return Val;
};
for (auto *EnumConstant : EnumD->enumerators()) {
ExpectedCases.insert(
std::make_pair(Normalize(EnumConstant->getInitVal()), EnumConstant));
}
for (const SwitchCase *CaseList = Switch->getSwitchCaseList(); CaseList;
CaseList = CaseList->getNextSwitchCase()) {
// Default likely intends to cover cases we'd insert.
if (isa<DefaultStmt>(CaseList))
return false;
const CaseStmt *CS = cast<CaseStmt>(CaseList);
// Case statement covers multiple values, so just counting doesn't work.
// GNU range cases are rare, we don't support them.
if (CS->caseStmtIsGNURange())
return false;
@ -135,48 +165,36 @@ bool PopulateSwitch::prepare(const Selection &Sel) {
const ConstantExpr *CE = dyn_cast<ConstantExpr>(CS->getLHS());
if (!CE || CE->isValueDependent())
return false;
// Unsure if this case could ever come up, but prevents an unreachable
// executing in getResultAsAPSInt.
if (CE->getResultStorageKind() == ConstantExpr::RSK_None)
return false;
auto Iter = ExpectedCases.find(Normalize(CE->getResultAsAPSInt()));
if (Iter != ExpectedCases.end())
Iter->second.setCovered();
}
// Only suggest tweak if we have more enumerators than cases.
return I != E;
return !llvm::all_of(ExpectedCases,
[](auto &Pair) { return Pair.second.isCovered(); });
}
Expected<Tweak::Effect> PopulateSwitch::apply(const Selection &Sel) {
ASTContext &Ctx = Sel.AST->getASTContext();
// Get the enum's integer width and signedness, for adjusting case literals.
unsigned EnumIntWidth = Ctx.getIntWidth(QualType(EnumT, 0));
bool EnumIsSigned = EnumT->isSignedIntegerOrEnumerationType();
llvm::SmallSet<llvm::APSInt, 32> ExistingEnumerators;
for (const SwitchCase *CaseList = Switch->getSwitchCaseList(); CaseList;
CaseList = CaseList->getNextSwitchCase()) {
const CaseStmt *CS = cast<CaseStmt>(CaseList);
assert(!CS->caseStmtIsGNURange());
const ConstantExpr *CE = cast<ConstantExpr>(CS->getLHS());
assert(!CE->isValueDependent());
llvm::APSInt Val = CE->getResultAsAPSInt();
Val = Val.extOrTrunc(EnumIntWidth);
Val.setIsSigned(EnumIsSigned);
ExistingEnumerators.insert(Val);
}
SourceLocation Loc = Body->getRBracLoc();
ASTContext &DeclASTCtx = DeclCtx->getParentASTContext();
std::string Text;
for (EnumConstantDecl *Enumerator : EnumD->enumerators()) {
if (ExistingEnumerators.contains(Enumerator->getInitVal()))
llvm::SmallString<256> Text;
for (auto &EnumConstant : ExpectedCases) {
// Skip any enum constants already covered
if (EnumConstant.second.isCovered())
continue;
Text += "case ";
Text += getQualification(DeclASTCtx, DeclCtx, Loc, EnumD);
if (EnumD->isScoped()) {
Text += EnumD->getName();
Text += "::";
}
Text += Enumerator->getName();
Text += ":";
Text.append({"case ", getQualification(DeclASTCtx, DeclCtx, Loc, EnumD)});
if (EnumD->isScoped())
Text.append({EnumD->getName(), "::"});
Text.append({EnumConstant.second.getEnumConstant()->getName(), ":"});
}
assert(!Text.empty() && "No enumerators to insert!");

View File

@ -2980,6 +2980,18 @@ TEST_F(PopulateSwitchTest, Test) {
void function() { switch (ns::A) {case ns::A:break;} }
)"",
},
{
// Duplicated constant names
Function,
R""(enum Enum {A,B,b=B}; ^switch (A) {})"",
R""(enum Enum {A,B,b=B}; switch (A) {case A:case B:break;})"",
},
{
// Duplicated constant names all in switch
Function,
R""(enum Enum {A,B,b=B}; ^switch (A) {case A:case B:break;})"",
"unavailable",
},
};
for (const auto &Case : Cases) {

View File

@ -14,6 +14,7 @@
#define LLVM_ADT_DENSEMAPINFO_H
#include "llvm/ADT/APInt.h"
#include "llvm/ADT/APSInt.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/Hashing.h"
#include "llvm/ADT/StringRef.h"
@ -371,6 +372,26 @@ template <> struct DenseMapInfo<APInt> {
}
};
/// Provide DenseMapInfo for APSInt, using the DenseMapInfo for APInt.
template <> struct DenseMapInfo<APSInt> {
static inline APSInt getEmptyKey() {
return APSInt(DenseMapInfo<APInt>::getEmptyKey());
}
static inline APSInt getTombstoneKey() {
return APSInt(DenseMapInfo<APInt>::getTombstoneKey());
}
static unsigned getHashValue(const APSInt &Key) {
return static_cast<unsigned>(hash_value(Key));
}
static bool isEqual(const APSInt &LHS, const APSInt &RHS) {
return LHS.getBitWidth() == RHS.getBitWidth() &&
LHS.isUnsigned() == RHS.isUnsigned() && LHS == RHS;
}
};
} // end namespace llvm
#endif // LLVM_ADT_DENSEMAPINFO_H