[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/SourceLocation.h"
#include "clang/Basic/SourceManager.h" #include "clang/Basic/SourceManager.h"
#include "clang/Tooling/Core/Replacement.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 <cassert>
#include <string> #include <string>
@ -57,11 +58,27 @@ class PopulateSwitch : public Tweak {
} }
private: 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 DeclContext *DeclCtx = nullptr;
const SwitchStmt *Switch = nullptr; const SwitchStmt *Switch = nullptr;
const CompoundStmt *Body = nullptr; const CompoundStmt *Body = nullptr;
const EnumType *EnumT = nullptr; const EnumType *EnumT = nullptr;
const EnumDecl *EnumD = 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) REGISTER_TWEAK(PopulateSwitch)
@ -112,21 +129,34 @@ bool PopulateSwitch::prepare(const Selection &Sel) {
if (!EnumD) if (!EnumD)
return false; return false;
// We trigger if there are fewer cases than enum values (and no case covers // We trigger if there are any values in the enum that aren't covered by the
// multiple values). This guarantees we'll have at least one case to insert. // switch.
// We don't yet determine what the cases are, as that means evaluating
// expressions.
auto I = EnumD->enumerator_begin();
auto E = EnumD->enumerator_end();
for (const SwitchCase *CaseList = Switch->getSwitchCaseList(); ASTContext &Ctx = Sel.AST->getASTContext();
CaseList && I != E; CaseList = CaseList->getNextSwitchCase(), I++) {
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. // Default likely intends to cover cases we'd insert.
if (isa<DefaultStmt>(CaseList)) if (isa<DefaultStmt>(CaseList))
return false; return false;
const CaseStmt *CS = cast<CaseStmt>(CaseList); 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()) if (CS->caseStmtIsGNURange())
return false; return false;
@ -135,48 +165,36 @@ bool PopulateSwitch::prepare(const Selection &Sel) {
const ConstantExpr *CE = dyn_cast<ConstantExpr>(CS->getLHS()); const ConstantExpr *CE = dyn_cast<ConstantExpr>(CS->getLHS());
if (!CE || CE->isValueDependent()) if (!CE || CE->isValueDependent())
return false; 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 !llvm::all_of(ExpectedCases,
return I != E; [](auto &Pair) { return Pair.second.isCovered(); });
} }
Expected<Tweak::Effect> PopulateSwitch::apply(const Selection &Sel) { Expected<Tweak::Effect> PopulateSwitch::apply(const Selection &Sel) {
ASTContext &Ctx = Sel.AST->getASTContext(); 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(); SourceLocation Loc = Body->getRBracLoc();
ASTContext &DeclASTCtx = DeclCtx->getParentASTContext(); ASTContext &DeclASTCtx = DeclCtx->getParentASTContext();
std::string Text; llvm::SmallString<256> Text;
for (EnumConstantDecl *Enumerator : EnumD->enumerators()) { for (auto &EnumConstant : ExpectedCases) {
if (ExistingEnumerators.contains(Enumerator->getInitVal())) // Skip any enum constants already covered
if (EnumConstant.second.isCovered())
continue; continue;
Text += "case "; Text.append({"case ", getQualification(DeclASTCtx, DeclCtx, Loc, EnumD)});
Text += getQualification(DeclASTCtx, DeclCtx, Loc, EnumD); if (EnumD->isScoped())
if (EnumD->isScoped()) { Text.append({EnumD->getName(), "::"});
Text += EnumD->getName(); Text.append({EnumConstant.second.getEnumConstant()->getName(), ":"});
Text += "::";
}
Text += Enumerator->getName();
Text += ":";
} }
assert(!Text.empty() && "No enumerators to insert!"); 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;} } 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) { for (const auto &Case : Cases) {

View File

@ -14,6 +14,7 @@
#define LLVM_ADT_DENSEMAPINFO_H #define LLVM_ADT_DENSEMAPINFO_H
#include "llvm/ADT/APInt.h" #include "llvm/ADT/APInt.h"
#include "llvm/ADT/APSInt.h"
#include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/Hashing.h" #include "llvm/ADT/Hashing.h"
#include "llvm/ADT/StringRef.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 } // end namespace llvm
#endif // LLVM_ADT_DENSEMAPINFO_H #endif // LLVM_ADT_DENSEMAPINFO_H