blob: c6d59340d30ad5aa9b23ca0aecd6399ed4de7820 [file] [log] [blame]
//===--- FillInMissingSwitchEnumCases.cpp - ------------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "clang/AST/AST.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
#include "clang/AST/Expr.h"
#include "clang/AST/NestedNameSpecifier.h"
#include "clang/Edit/RefactoringFixits.h"
#include <unordered_map>
using namespace clang;
namespace {
struct CaseInfo {
const SwitchCase *Case, *NextCase;
unsigned Index;
};
typedef std::unordered_map<int64_t, CaseInfo> CoveredEnumCasesInfoType;
/// Return true if the ordering of the covered enum cases is similar to the
/// order of the enum case constants that are defined in the enum.
bool useCaseBasedOrdering(const ArrayRef<int64_t> &CoveredEnumCaseValues,
const CoveredEnumCasesInfoType &CoveredEnumCases) {
if (CoveredEnumCaseValues.empty())
return false;
for (const auto &I : llvm::enumerate(CoveredEnumCaseValues)) {
auto It = CoveredEnumCases.find(I.value());
if (It == CoveredEnumCases.end())
return false;
const CaseInfo &Case = It->second;
if (Case.Index != I.index())
return false;
}
return true;
}
/// Determine if the inserted cases should be wrapped in braces using a simple
/// heuristic:
/// Wrap only if at least 90% of existing cases use braces.
bool useBraces(const SwitchStmt *S) {
unsigned CaseCount = 0, CompoundCasesCount = 0;
for (const SwitchCase *Case = S->getSwitchCaseList(); Case;
Case = Case->getNextSwitchCase(), ++CaseCount) {
if (!Case->getSubStmt())
continue;
if (isa<CompoundStmt>(Case->getSubStmt()))
++CompoundCasesCount;
}
return CaseCount && float(CompoundCasesCount) / float(CaseCount) >= 0.9;
}
} // end anonymous namespace
void edit::fillInMissingSwitchEnumCases(
ASTContext &Context, const SwitchStmt *Switch, const EnumDecl *Enum,
const DeclContext *SwitchContext,
llvm::function_ref<void(const FixItHint &)> Consumer) {
// Compute the number of cases in the switch.
unsigned CaseCount = 0;
for (const SwitchCase *Case = Switch->getSwitchCaseList(); Case;
Case = Case->getNextSwitchCase())
++CaseCount;
// Compute the set of enum values that are covered by the switch.
CoveredEnumCasesInfoType CoveredEnumCases;
const SwitchCase *DefaultCase = nullptr;
const SwitchCase *FirstCoveredEnumCase = nullptr;
const SwitchCase *NextCase = nullptr;
unsigned CaseIndex = CaseCount;
for (const SwitchCase *Case = Switch->getSwitchCaseList(); Case;
NextCase = Case, Case = Case->getNextSwitchCase()) {
// The cases in the switch are ordered back to front, so the index has
// to be reversed.
--CaseIndex;
if (isa<DefaultStmt>(Case)) {
DefaultCase = Case;
continue;
}
const auto *CS = cast<CaseStmt>(Case);
if (const auto *LHS = CS->getLHS()) {
llvm::APSInt Value;
if (!LHS->EvaluateAsInt(Value, Context))
continue;
// Only allow constant that fix into 64 bits.
if (Value.getMinSignedBits() > 64)
continue;
CoveredEnumCases[Value.getSExtValue()] =
CaseInfo{Case, NextCase, CaseIndex};
// The cases in the switch are ordered back to front, so the last
// case is actually the first enum case in the switch.
FirstCoveredEnumCase = Case;
}
}
// Wrap the inserted cases in braces using a simple heuristic:
// Wrap only if at least 90% of existing cases use braces.
bool WrapInBraces = useBraces(Switch);
auto CreateReplacementForMissingCaseGroup =
[&](ArrayRef<const EnumConstantDecl *> UncoveredEnumCases,
SourceLocation InsertionLoc = SourceLocation()) {
if (UncoveredEnumCases.empty())
return;
std::string Result;
llvm::raw_string_ostream OS(Result);
for (const auto *EnumCase : UncoveredEnumCases) {
OS << "case ";
if (SwitchContext) {
const auto *NS = NestedNameSpecifier::getRequiredQualification(
Context, SwitchContext, Enum->getLexicalDeclContext());
if (NS)
NS->print(OS, Context.getPrintingPolicy());
}
if (Enum->isScoped())
OS << Enum->getName() << "::";
OS << EnumCase->getName() << ":";
if (WrapInBraces)
OS << " {";
OS << "\n<#code#>\nbreak;\n";
if (WrapInBraces)
OS << "}\n";
}
if (InsertionLoc.isInvalid()) {
// Insert the cases before the 'default' if it's the last case in the
// switch.
// Note: Switch cases are ordered back to front, so the last default
// case would be the first case in the switch statement.
if (DefaultCase && DefaultCase == Switch->getSwitchCaseList())
InsertionLoc = DefaultCase->getLocStart();
else
InsertionLoc = Switch->getBody()->getLocEnd();
}
Consumer(FixItHint::CreateInsertion(
Context.getSourceManager().getSpellingLoc(InsertionLoc), OS.str()));
};
// Determine which enum cases are uncovered.
llvm::SmallVector<std::pair<const EnumConstantDecl *, int64_t>, 8> EnumCases;
llvm::SmallVector<int64_t, 8> CoveredEnumCaseValues;
for (const auto *EnumCase : Enum->enumerators()) {
if (EnumCase->getInitVal().getMinSignedBits() > 64)
continue;
int64_t Value = EnumCase->getInitVal().getSExtValue();
EnumCases.push_back(std::make_pair(EnumCase, Value));
if (CoveredEnumCases.count(Value))
CoveredEnumCaseValues.push_back(Value);
}
llvm::SmallVector<const EnumConstantDecl *, 8> UncoveredEnumCases;
// Figure out if the ordering of the covered enum cases is similar to the
// order of enum case values defined in the enum.
if (useCaseBasedOrdering(CoveredEnumCaseValues, CoveredEnumCases)) {
// Start inserting before the first covered case.
SourceLocation InsertionLoc = FirstCoveredEnumCase->getLocStart();
for (const auto &EnumCase : EnumCases) {
if (!CoveredEnumCases.count(EnumCase.second)) {
UncoveredEnumCases.push_back(EnumCase.first);
continue;
}
// Create the insertion source replacement for this set of uncovered
// cases.
CreateReplacementForMissingCaseGroup(UncoveredEnumCases, InsertionLoc);
UncoveredEnumCases.clear();
// Find the insertion location for the next set of uncovered cases.
auto It = CoveredEnumCases.find(EnumCase.second);
assert(It != CoveredEnumCases.end() && "Missing enum case");
const CaseInfo &Case = It->second;
InsertionLoc = Case.NextCase ? Case.NextCase->getLocStart()
: /*Insert before end*/ SourceLocation();
}
CreateReplacementForMissingCaseGroup(UncoveredEnumCases, InsertionLoc);
} else {
// Gather all of the uncovered enum cases.
for (const auto &EnumCase : EnumCases) {
if (!CoveredEnumCases.count(EnumCase.second))
UncoveredEnumCases.push_back(EnumCase.first);
}
assert(!UncoveredEnumCases.empty() &&
"Can't fill-in enum cases in a full switch");
CreateReplacementForMissingCaseGroup(UncoveredEnumCases);
}
}