199 changes: 199 additions & 0 deletions clang-tools-extra/clang-tidy/bugprone/TaggedUnionMemberCountCheck.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
//===--- TaggedUnionMemberCountCheck.cpp - clang-tidy ---------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "TaggedUnionMemberCountCheck.h"
#include "../utils/OptionsUtils.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallSet.h"

using namespace clang::ast_matchers;

namespace clang::tidy::bugprone {

static constexpr llvm::StringLiteral StrictModeOptionName = "StrictMode";
static constexpr llvm::StringLiteral EnableCountingEnumHeuristicOptionName =
"EnableCountingEnumHeuristic";
static constexpr llvm::StringLiteral CountingEnumPrefixesOptionName =
"CountingEnumPrefixes";
static constexpr llvm::StringLiteral CountingEnumSuffixesOptionName =
"CountingEnumSuffixes";

static constexpr bool StrictModeOptionDefaultValue = false;
static constexpr bool EnableCountingEnumHeuristicOptionDefaultValue = true;
static constexpr llvm::StringLiteral CountingEnumPrefixesOptionDefaultValue =
"";
static constexpr llvm::StringLiteral CountingEnumSuffixesOptionDefaultValue =
"count";

static constexpr llvm::StringLiteral RootMatchBindName = "root";
static constexpr llvm::StringLiteral UnionMatchBindName = "union";
static constexpr llvm::StringLiteral TagMatchBindName = "tags";

namespace {

AST_MATCHER_P2(RecordDecl, fieldCountOfKindIsOne,
ast_matchers::internal::Matcher<FieldDecl>, InnerMatcher,
StringRef, BindName) {
// BoundNodesTreeBuilder resets itself when a match occurs.
// So to avoid losing previously saved binds, a temporary instance
// is used for matching.
//
// For precedence, see commit: 5b07de1a5faf4a22ae6fd982b877c5e7e3a76559
clang::ast_matchers::internal::BoundNodesTreeBuilder TempBuilder;

const FieldDecl *FirstMatch = nullptr;
for (const FieldDecl *Field : Node.fields()) {
if (InnerMatcher.matches(*Field, Finder, &TempBuilder)) {
if (FirstMatch) {
return false;
} else {
FirstMatch = Field;
}
}
}

if (FirstMatch) {
Builder->setBinding(BindName, clang::DynTypedNode::create(*FirstMatch));
return true;
}
return false;
}

} // namespace

TaggedUnionMemberCountCheck::TaggedUnionMemberCountCheck(
StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
StrictMode(
Options.get(StrictModeOptionName, StrictModeOptionDefaultValue)),
EnableCountingEnumHeuristic(
Options.get(EnableCountingEnumHeuristicOptionName,
EnableCountingEnumHeuristicOptionDefaultValue)),
CountingEnumPrefixes(utils::options::parseStringList(
Options.get(CountingEnumPrefixesOptionName,
CountingEnumPrefixesOptionDefaultValue))),
CountingEnumSuffixes(utils::options::parseStringList(
Options.get(CountingEnumSuffixesOptionName,
CountingEnumSuffixesOptionDefaultValue))) {
if (!EnableCountingEnumHeuristic) {
if (Options.get(CountingEnumPrefixesOptionName))
configurationDiag("%0: Counting enum heuristic is disabled but "
"%1 is set")
<< Name << CountingEnumPrefixesOptionName;
if (Options.get(CountingEnumSuffixesOptionName))
configurationDiag("%0: Counting enum heuristic is disabled but "
"%1 is set")
<< Name << CountingEnumSuffixesOptionName;
}
}

void TaggedUnionMemberCountCheck::storeOptions(
ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, StrictModeOptionName, StrictMode);
Options.store(Opts, EnableCountingEnumHeuristicOptionName,
EnableCountingEnumHeuristic);
Options.store(Opts, CountingEnumPrefixesOptionName,
utils::options::serializeStringList(CountingEnumPrefixes));
Options.store(Opts, CountingEnumSuffixesOptionName,
utils::options::serializeStringList(CountingEnumSuffixes));
}

void TaggedUnionMemberCountCheck::registerMatchers(MatchFinder *Finder) {

auto UnionField = fieldDecl(hasType(qualType(
hasCanonicalType(recordType(hasDeclaration(recordDecl(isUnion())))))));

auto EnumField = fieldDecl(hasType(
qualType(hasCanonicalType(enumType(hasDeclaration(enumDecl()))))));

auto hasOneUnionField = fieldCountOfKindIsOne(UnionField, UnionMatchBindName);
auto hasOneEnumField = fieldCountOfKindIsOne(EnumField, TagMatchBindName);

Finder->addMatcher(recordDecl(anyOf(isStruct(), isClass()), hasOneUnionField,
hasOneEnumField, unless(isImplicit()))
.bind(RootMatchBindName),
this);
}

bool TaggedUnionMemberCountCheck::isCountingEnumLikeName(StringRef Name) const {
if (llvm::any_of(CountingEnumPrefixes, [Name](StringRef Prefix) -> bool {
return Name.starts_with_insensitive(Prefix);
}))
return true;
if (llvm::any_of(CountingEnumSuffixes, [Name](StringRef Suffix) -> bool {
return Name.ends_with_insensitive(Suffix);
}))
return true;
return false;
}

std::pair<const std::size_t, const EnumConstantDecl *>
TaggedUnionMemberCountCheck::getNumberOfEnumValues(const EnumDecl *ED) {
llvm::SmallSet<llvm::APSInt, 16> EnumValues;

const EnumConstantDecl *LastEnumConstant = nullptr;
for (const EnumConstantDecl *Enumerator : ED->enumerators()) {
EnumValues.insert(Enumerator->getInitVal());
LastEnumConstant = Enumerator;
}

if (EnableCountingEnumHeuristic && LastEnumConstant &&
isCountingEnumLikeName(LastEnumConstant->getName()) &&
(LastEnumConstant->getInitVal() == (EnumValues.size() - 1))) {
return {EnumValues.size() - 1, LastEnumConstant};
}

return {EnumValues.size(), nullptr};
}

void TaggedUnionMemberCountCheck::check(
const MatchFinder::MatchResult &Result) {
const auto *Root = Result.Nodes.getNodeAs<RecordDecl>(RootMatchBindName);
const auto *UnionField =
Result.Nodes.getNodeAs<FieldDecl>(UnionMatchBindName);
const auto *TagField = Result.Nodes.getNodeAs<FieldDecl>(TagMatchBindName);

assert(Root && "Root is missing!");
assert(UnionField && "UnionField is missing!");
assert(TagField && "TagField is missing!");
if (!Root || !UnionField || !TagField)
return;

const auto *UnionDef =
UnionField->getType().getCanonicalType().getTypePtr()->getAsRecordDecl();
const auto *EnumDef = llvm::dyn_cast<EnumDecl>(
TagField->getType().getCanonicalType().getTypePtr()->getAsTagDecl());

assert(UnionDef && "UnionDef is missing!");
assert(EnumDef && "EnumDef is missing!");
if (!UnionDef || !EnumDef)
return;

const std::size_t UnionMemberCount = llvm::range_size(UnionDef->fields());
auto [TagCount, CountingEnumConstantDecl] = getNumberOfEnumValues(EnumDef);

if (UnionMemberCount > TagCount) {
diag(Root->getLocation(),
"tagged union has more data members (%0) than tags (%1)!")
<< UnionMemberCount << TagCount;
} else if (StrictMode && UnionMemberCount < TagCount) {
diag(Root->getLocation(),
"tagged union has fewer data members (%0) than tags (%1)!")
<< UnionMemberCount << TagCount;
}

if (CountingEnumConstantDecl) {
diag(CountingEnumConstantDecl->getLocation(),
"assuming that this constant is just an auxiliary value and not "
"used for indicating a valid union data member",
DiagnosticIDs::Note);
}
}

} // namespace clang::tidy::bugprone
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//===--- TaggedUnionMemberCountCheck.h - clang-tidy -------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_TAGGEDUNIONMEMBERCOUNTCHECK_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_TAGGEDUNIONMEMBERCOUNTCHECK_H

#include "../ClangTidyCheck.h"

namespace clang::tidy::bugprone {

/// Gives warnings for tagged unions, where the number of tags is
/// different from the number of data members inside the union.
///
/// For the user-facing documentation see:
/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/tagged-union-member-count.html
class TaggedUnionMemberCountCheck : public ClangTidyCheck {
public:
TaggedUnionMemberCountCheck(StringRef Name, ClangTidyContext *Context);
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;

private:
const bool StrictMode;
const bool EnableCountingEnumHeuristic;
const std::vector<StringRef> CountingEnumPrefixes;
const std::vector<StringRef> CountingEnumSuffixes;

std::pair<const std::size_t, const EnumConstantDecl *>
getNumberOfEnumValues(const EnumDecl *ED);
bool isCountingEnumLikeName(StringRef Name) const;
};

} // namespace clang::tidy::bugprone

#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_TAGGEDUNIONMEMBERCOUNTCHECK_H
16 changes: 9 additions & 7 deletions clang-tools-extra/clang-tidy/misc/UnusedUsingDeclsCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ AST_MATCHER_P(DeducedTemplateSpecializationType, refsToTemplatedDecl,
return false;
}

AST_MATCHER_P(Type, asTagDecl, clang::ast_matchers::internal::Matcher<TagDecl>,
DeclMatcher) {
if (const TagDecl *ND = Node.getAsTagDecl())
return DeclMatcher.matches(*ND, Finder, Builder);
return false;
}

} // namespace

// A function that helps to tell whether a TargetDecl in a UsingDecl will be
Expand Down Expand Up @@ -61,7 +68,8 @@ void UnusedUsingDeclsCheck::registerMatchers(MatchFinder *Finder) {
Finder->addMatcher(userDefinedLiteral().bind("used"), this);
Finder->addMatcher(
loc(elaboratedType(unless(hasQualifier(nestedNameSpecifier())),
hasUnqualifiedDesugaredType(type().bind("usedType")))),
hasUnqualifiedDesugaredType(
type(asTagDecl(tagDecl().bind("used")))))),
this);
// Cases where we can identify the UsingShadowDecl directly, rather than
// just its target.
Expand Down Expand Up @@ -139,12 +147,6 @@ void UnusedUsingDeclsCheck::check(const MatchFinder::MatchResult &Result) {
return;
}

if (const auto *T = Result.Nodes.getNodeAs<Type>("usedType")) {
if (const auto *ND = T->getAsTagDecl())
RemoveNamedDecl(ND);
return;
}

if (const auto *UsedShadow =
Result.Nodes.getNodeAs<UsingShadowDecl>("usedShadow")) {
removeFromFoundDecls(UsedShadow->getTargetDecl());
Expand Down
25 changes: 15 additions & 10 deletions clang-tools-extra/clang-tidy/modernize/LoopConvertUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -777,7 +777,7 @@ bool ForLoopIndexUseVisitor::TraverseLambdaCapture(LambdaExpr *LE,
const LambdaCapture *C,
Expr *Init) {
if (C->capturesVariable()) {
const ValueDecl *VDecl = C->getCapturedVar();
ValueDecl *VDecl = C->getCapturedVar();
if (areSameVariable(IndexVar, VDecl)) {
// FIXME: if the index is captured, it will count as an usage and the
// alias (if any) won't work, because it is only used in case of having
Expand All @@ -787,6 +787,8 @@ bool ForLoopIndexUseVisitor::TraverseLambdaCapture(LambdaExpr *LE,
: Usage::UK_CaptureByRef,
C->getLocation()));
}
if (VDecl->isInitCapture())
TraverseStmtImpl(cast<VarDecl>(VDecl)->getInit());
}
return VisitorBase::TraverseLambdaCapture(LE, C, Init);
}
Expand Down Expand Up @@ -816,6 +818,17 @@ bool ForLoopIndexUseVisitor::VisitDeclStmt(DeclStmt *S) {
return true;
}

bool ForLoopIndexUseVisitor::TraverseStmtImpl(Stmt *S) {
// All this pointer swapping is a mechanism for tracking immediate parentage
// of Stmts.
const Stmt *OldNextParent = NextStmtParent;
CurrStmtParent = NextStmtParent;
NextStmtParent = S;
bool Result = VisitorBase::TraverseStmt(S);
NextStmtParent = OldNextParent;
return Result;
}

bool ForLoopIndexUseVisitor::TraverseStmt(Stmt *S) {
// If this is an initialization expression for a lambda capture, prune the
// traversal so that we don't end up diagnosing the contained DeclRefExpr as
Expand All @@ -828,15 +841,7 @@ bool ForLoopIndexUseVisitor::TraverseStmt(Stmt *S) {
return true;
}
}

// All this pointer swapping is a mechanism for tracking immediate parentage
// of Stmts.
const Stmt *OldNextParent = NextStmtParent;
CurrStmtParent = NextStmtParent;
NextStmtParent = S;
bool Result = VisitorBase::TraverseStmt(S);
NextStmtParent = OldNextParent;
return Result;
return TraverseStmtImpl(S);
}

std::string VariableNamer::createIndexName() {
Expand Down
2 changes: 2 additions & 0 deletions clang-tools-extra/clang-tidy/modernize/LoopConvertUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,8 @@ class ForLoopIndexUseVisitor
bool VisitDeclStmt(DeclStmt *S);
bool TraverseStmt(Stmt *S);

bool TraverseStmtImpl(Stmt *S);

/// Add an expression to the list of expressions on which the container
/// expression depends.
void addComponent(const Expr *E);
Expand Down
1 change: 1 addition & 0 deletions clang-tools-extra/clangd/ClangdServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,7 @@ void ClangdServer::codeComplete(PathRef File, Position Pos,

CodeCompleteOpts.MainFileSignals = IP->Signals;
CodeCompleteOpts.AllScopes = Config::current().Completion.AllScopes;
CodeCompleteOpts.ArgumentLists = Config::current().Completion.ArgumentLists;
// FIXME(ibiryukov): even if Preamble is non-null, we may want to check
// both the old and the new version in case only one of them matches.
CodeCompleteResult Result = clangd::codeComplete(
Expand Down
31 changes: 22 additions & 9 deletions clang-tools-extra/clangd/CodeComplete.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "AST.h"
#include "CodeCompletionStrings.h"
#include "Compiler.h"
#include "Config.h"
#include "ExpectedTypes.h"
#include "Feature.h"
#include "FileDistance.h"
Expand Down Expand Up @@ -350,8 +351,7 @@ struct CodeCompletionBuilder {
CodeCompletionContext::Kind ContextKind,
const CodeCompleteOptions &Opts,
bool IsUsingDeclaration, tok::TokenKind NextTokenKind)
: ASTCtx(ASTCtx),
EnableFunctionArgSnippets(Opts.EnableFunctionArgSnippets),
: ASTCtx(ASTCtx), ArgumentLists(Opts.ArgumentLists),
IsUsingDeclaration(IsUsingDeclaration), NextTokenKind(NextTokenKind) {
Completion.Deprecated = true; // cleared by any non-deprecated overload.
add(C, SemaCCS, ContextKind);
Expand Down Expand Up @@ -561,14 +561,23 @@ struct CodeCompletionBuilder {
}

std::string summarizeSnippet() const {
/// localize ArgumentLists tests for better readability
const bool None = ArgumentLists == Config::ArgumentListsPolicy::None;
const bool Open =
ArgumentLists == Config::ArgumentListsPolicy::OpenDelimiter;
const bool Delim = ArgumentLists == Config::ArgumentListsPolicy::Delimiters;
const bool Full =
ArgumentLists == Config::ArgumentListsPolicy::FullPlaceholders ||
(!None && !Open && !Delim); // <-- failsafe: Full is default

if (IsUsingDeclaration)
return "";
auto *Snippet = onlyValue<&BundledEntry::SnippetSuffix>();
if (!Snippet)
// All bundles are function calls.
// FIXME(ibiryukov): sometimes add template arguments to a snippet, e.g.
// we need to complete 'forward<$1>($0)'.
return "($0)";
return None ? "" : (Open ? "(" : "($0)");

if (Snippet->empty())
return "";
Expand Down Expand Up @@ -607,7 +616,7 @@ struct CodeCompletionBuilder {
return "";
}
}
if (EnableFunctionArgSnippets)
if (Full)
return *Snippet;

// Replace argument snippets with a simplified pattern.
Expand All @@ -622,9 +631,9 @@ struct CodeCompletionBuilder {

bool EmptyArgs = llvm::StringRef(*Snippet).ends_with("()");
if (Snippet->front() == '<')
return EmptyArgs ? "<$1>()$0" : "<$1>($0)";
return None ? "" : (Open ? "<" : (EmptyArgs ? "<$1>()$0" : "<$1>($0)"));
if (Snippet->front() == '(')
return EmptyArgs ? "()" : "($0)";
return None ? "" : (Open ? "(" : (EmptyArgs ? "()" : "($0)"));
return *Snippet; // Not an arg snippet?
}
// 'CompletionItemKind::Interface' matches template type aliases.
Expand All @@ -638,7 +647,7 @@ struct CodeCompletionBuilder {
// e.g. Foo<${1:class}>.
if (llvm::StringRef(*Snippet).ends_with("<>"))
return "<>"; // can happen with defaulted template arguments.
return "<$0>";
return None ? "" : (Open ? "<" : "<$0>");
}
return *Snippet;
}
Expand All @@ -654,7 +663,8 @@ struct CodeCompletionBuilder {
ASTContext *ASTCtx;
CodeCompletion Completion;
llvm::SmallVector<BundledEntry, 1> Bundled;
bool EnableFunctionArgSnippets;
/// the way argument lists are handled.
Config::ArgumentListsPolicy ArgumentLists;
// No snippets will be generated for using declarations and when the function
// arguments are already present.
bool IsUsingDeclaration;
Expand Down Expand Up @@ -1409,6 +1419,9 @@ bool semaCodeComplete(std::unique_ptr<CodeCompleteConsumer> Consumer,
Clang->getPreprocessorOpts().SingleFileParseMode = CompletingInPreamble;
Clang->setCodeCompletionConsumer(Consumer.release());

if (Input.Preamble.RequiredModules)
Input.Preamble.RequiredModules->adjustHeaderSearchOptions(Clang->getHeaderSearchOpts());

SyntaxOnlyAction Action;
if (!Action.BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0])) {
log("BeginSourceFile() failed when running codeComplete for {0}",
Expand Down Expand Up @@ -2122,7 +2135,7 @@ clang::CodeCompleteOptions CodeCompleteOptions::getClangCompleteOpts() const {
// When an is used, Sema is responsible for completing the main file,
// the index can provide results from the preamble.
// Tell Sema not to deserialize the preamble to look for results.
Result.LoadExternal = !Index;
Result.LoadExternal = ForceLoadPreamble || !Index;
Result.IncludeFixIts = IncludeFixIts;

return Result;
Expand Down
14 changes: 10 additions & 4 deletions clang-tools-extra/clangd/CodeComplete.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#include "ASTSignals.h"
#include "Compiler.h"
#include "Config.h"
#include "Protocol.h"
#include "Quality.h"
#include "index/Index.h"
Expand Down Expand Up @@ -52,6 +53,11 @@ struct CodeCompleteOptions {
/// For example, private members are usually inaccessible.
bool IncludeIneligibleResults = false;

/// Force sema to load decls from preamble even if an index is provided.
/// This is helpful for cases the index can't provide symbols, e.g. with
/// experimental c++20 modules
bool ForceLoadPreamble = false;

/// Combine overloads into a single completion item where possible.
/// If none, the implementation may choose an appropriate behavior.
/// (In practice, ClangdLSPServer enables bundling if the client claims
Expand Down Expand Up @@ -96,17 +102,17 @@ struct CodeCompleteOptions {
/// '->' on member access etc.
bool IncludeFixIts = false;

/// Whether to generate snippets for function arguments on code-completion.
/// Needs snippets to be enabled as well.
bool EnableFunctionArgSnippets = true;

/// Whether to include index symbols that are not defined in the scopes
/// visible from the code completion point. This applies in contexts without
/// explicit scope qualifiers.
///
/// Such completions can insert scope qualifiers.
bool AllScopes = false;

/// The way argument list on calls '()' and generics '<>' are handled.
Config::ArgumentListsPolicy ArgumentLists =
Config::ArgumentListsPolicy::FullPlaceholders;

/// Whether to use the clang parser, or fallback to text-based completion
/// (using identifiers in the current file and symbol indexes).
enum CodeCompletionParse {
Expand Down
14 changes: 14 additions & 0 deletions clang-tools-extra/clangd/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -126,11 +126,25 @@ struct Config {
std::vector<std::string> FullyQualifiedNamespaces;
} Style;

/// controls the completion options for argument lists.
enum class ArgumentListsPolicy {
/// nothing, no argument list and also NO Delimiters "()" or "<>".
None,
/// open, only opening delimiter "(" or "<".
OpenDelimiter,
/// empty pair of delimiters "()" or "<>".
Delimiters,
/// full name of both type and variable.
FullPlaceholders,
};

/// Configures code completion feature.
struct {
/// Whether code completion includes results that are not visible in current
/// scopes.
bool AllScopes = true;
/// controls the completion options for argument lists.
ArgumentListsPolicy ArgumentLists = ArgumentListsPolicy::FullPlaceholders;
} Completion;

/// Configures hover feature.
Expand Down
15 changes: 15 additions & 0 deletions clang-tools-extra/clangd/ConfigCompile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,21 @@ struct FragmentCompiler {
C.Completion.AllScopes = AllScopes;
});
}
if (F.ArgumentLists) {
if (auto Val =
compileEnum<Config::ArgumentListsPolicy>("ArgumentLists",
*F.ArgumentLists)
.map("None", Config::ArgumentListsPolicy::None)
.map("OpenDelimiter",
Config::ArgumentListsPolicy::OpenDelimiter)
.map("Delimiters", Config::ArgumentListsPolicy::Delimiters)
.map("FullPlaceholders",
Config::ArgumentListsPolicy::FullPlaceholders)
.value())
Out.Apply.push_back([Val](const Params &, Config &C) {
C.Completion.ArgumentLists = *Val;
});
}
}

void compile(Fragment::HoverBlock &&F) {
Expand Down
8 changes: 8 additions & 0 deletions clang-tools-extra/clangd/ConfigFragment.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_CONFIGFRAGMENT_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_CONFIGFRAGMENT_H

#include "Config.h"
#include "ConfigProvider.h"
#include "llvm/Support/SMLoc.h"
#include "llvm/Support/SourceMgr.h"
Expand Down Expand Up @@ -308,6 +309,13 @@ struct Fragment {
/// Whether code completion should include suggestions from scopes that are
/// not visible. The required scope prefix will be inserted.
std::optional<Located<bool>> AllScopes;
/// How to present the argument list between '()' and '<>':
/// valid values are enum Config::ArgumentListsPolicy values:
/// None: Nothing at all
/// OpenDelimiter: only opening delimiter "(" or "<"
/// Delimiters: empty pair of delimiters "()" or "<>"
/// FullPlaceholders: full name of both type and parameter
std::optional<Located<std::string>> ArgumentLists;
};
CompletionBlock Completion;

Expand Down
4 changes: 4 additions & 0 deletions clang-tools-extra/clangd/ConfigYAML.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,10 @@ class Parser {
if (auto AllScopes = boolValue(N, "AllScopes"))
F.AllScopes = *AllScopes;
});
Dict.handle("ArgumentLists", [&](Node &N) {
if (auto ArgumentLists = scalarValue(N, "ArgumentLists"))
F.ArgumentLists = *ArgumentLists;
});
Dict.parse(N);
}

Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clangd/FindSymbols.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ getWorkspaceSymbols(llvm::StringRef Query, int Limit,
*Req.Limit *= 5;
}
TopN<ScoredSymbolInfo, ScoredSymbolGreater> Top(
Req.Limit ? *Req.Limit : std::numeric_limits<size_t>::max());
Req.Limit.value_or(std::numeric_limits<size_t>::max()));
FuzzyMatcher Filter(Req.Query);

Index->fuzzyFind(Req, [HintPath, &Top, &Filter, AnyScope = Req.AnyScope,
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clangd/index/MemIndex.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ bool MemIndex::fuzzyFind(
trace::Span Tracer("MemIndex fuzzyFind");

TopN<std::pair<float, const Symbol *>> Top(
Req.Limit ? *Req.Limit : std::numeric_limits<size_t>::max());
Req.Limit.value_or(std::numeric_limits<size_t>::max()));
FuzzyMatcher Filter(Req.Query);
bool More = false;
for (const auto &Pair : Index) {
Expand Down
69 changes: 57 additions & 12 deletions clang-tools-extra/clangd/index/SymbolCollector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
Expand Down Expand Up @@ -75,18 +76,62 @@ bool isPrivateProtoDecl(const NamedDecl &ND) {
if (ND.getIdentifier() == nullptr)
return false;
auto Name = ND.getIdentifier()->getName();
if (!Name.contains('_'))
return false;
// Nested proto entities (e.g. Message::Nested) have top-level decls
// that shouldn't be used (Message_Nested). Ignore them completely.
// The nested entities are dangling type aliases, we may want to reconsider
// including them in the future.
// For enum constants, SOME_ENUM_CONSTANT is not private and should be
// indexed. Outer_INNER is private. This heuristic relies on naming style, it
// will include OUTER_INNER and exclude some_enum_constant.
// FIXME: the heuristic relies on naming style (i.e. no underscore in
// user-defined names) and can be improved.
return (ND.getKind() != Decl::EnumConstant) || llvm::any_of(Name, islower);
// There are some internal helpers like _internal_set_foo();
if (Name.contains("_internal_"))
return true;

// https://protobuf.dev/reference/cpp/cpp-generated/#nested-types
// Nested entities (messages/enums) has two names, one at the top-level scope,
// with a mangled name created by prepending all the outer types. These names
// are almost never preferred by the developers, so exclude them from index.
// e.g.
// message Foo {
// message Bar {}
// enum E { A }
// }
//
// yields:
// class Foo_Bar {};
// enum Foo_E { Foo_E_A };
// class Foo {
// using Bar = Foo_Bar;
// static constexpr Foo_E A = Foo_E_A;
// };

// We get rid of Foo_Bar and Foo_E by discarding any top-level entries with
// `_` in the name. This relies on original message/enum not having `_` in the
// name. Hence might go wrong in certain cases.
if (ND.getDeclContext()->isNamespace()) {
// Strip off some known public suffix helpers for enums, rest of the helpers
// are generated inside record decls so we don't care.
// https://protobuf.dev/reference/cpp/cpp-generated/#enum
Name.consume_back("_descriptor");
Name.consume_back("_IsValid");
Name.consume_back("_Name");
Name.consume_back("_Parse");
Name.consume_back("_MIN");
Name.consume_back("_MAX");
Name.consume_back("_ARRAYSIZE");
return Name.contains('_');
}

// EnumConstantDecls need some special attention, despite being nested in a
// TagDecl, they might still have mangled names. We filter those by checking
// if it has parent's name as a prefix.
// This might go wrong if a nested entity has a name that starts with parent's
// name, e.g: enum Foo { Foo_X }.
if (llvm::isa<EnumConstantDecl>(&ND)) {
auto *DC = llvm::cast<EnumDecl>(ND.getDeclContext());
if (!DC || !DC->getIdentifier())
return false;
auto CtxName = DC->getIdentifier()->getName();
return !CtxName.empty() && Name.consume_front(CtxName) &&
Name.consume_front("_");
}

// Now we're only left with fields/methods without an `_internal_` in the
// name, they're intended for public use.
return false;
}

// We only collect #include paths for symbols that are suitable for global code
Expand Down
2 changes: 1 addition & 1 deletion clang-tools-extra/clangd/index/dex/Dex.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ bool Dex::fuzzyFind(const FuzzyFindRequest &Req,
return LHS.second > RHS.second;
};
TopN<IDAndScore, decltype(Compare)> Top(
Req.Limit ? *Req.Limit : std::numeric_limits<size_t>::max(), Compare);
Req.Limit.value_or(std::numeric_limits<size_t>::max()), Compare);
for (const auto &IDAndScore : IDAndScores) {
const DocID SymbolDocID = IDAndScore.first;
const auto *Sym = Symbols[SymbolDocID];
Expand Down
1 change: 1 addition & 0 deletions clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ add_clang_library(clangDaemonTweaks OBJECT
RemoveUsingNamespace.cpp
ScopifyEnum.cpp
SpecialMembers.cpp
SwapBinaryOperands.cpp
SwapIfBranches.cpp

LINK_LIBS
Expand Down
217 changes: 217 additions & 0 deletions clang-tools-extra/clangd/refactor/tweaks/SwapBinaryOperands.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
//===--- SwapBinaryOperands.cpp ----------------------------------*- C++-*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "ParsedAST.h"
#include "Protocol.h"
#include "Selection.h"
#include "SourceCode.h"
#include "refactor/Tweak.h"
#include "support/Logger.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Expr.h"
#include "clang/AST/OperationKinds.h"
#include "clang/AST/Stmt.h"
#include "clang/Basic/LLVM.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Tooling/Core/Replacement.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/FormatVariadic.h"
#include <string>
#include <utility>

namespace clang {
namespace clangd {
namespace {
/// Check whether it makes logical sense to swap operands to an operator.
/// Assignment or member access operators are rarely swappable
/// while keeping the meaning intact, whereas comparison operators, mathematical
/// operators, etc. are often desired to be swappable for readability, avoiding
/// bugs by assigning to nullptr when comparison was desired, etc.
bool isOpSwappable(const BinaryOperatorKind Opcode) {
switch (Opcode) {
case BinaryOperatorKind::BO_Mul:
case BinaryOperatorKind::BO_Add:
case BinaryOperatorKind::BO_LT:
case BinaryOperatorKind::BO_GT:
case BinaryOperatorKind::BO_LE:
case BinaryOperatorKind::BO_GE:
case BinaryOperatorKind::BO_EQ:
case BinaryOperatorKind::BO_NE:
case BinaryOperatorKind::BO_And:
case BinaryOperatorKind::BO_Xor:
case BinaryOperatorKind::BO_Or:
case BinaryOperatorKind::BO_LAnd:
case BinaryOperatorKind::BO_LOr:
case BinaryOperatorKind::BO_Comma:
return true;
// Noncommutative operators:
case BinaryOperatorKind::BO_Div:
case BinaryOperatorKind::BO_Sub:
case BinaryOperatorKind::BO_Shl:
case BinaryOperatorKind::BO_Shr:
case BinaryOperatorKind::BO_Rem:
// <=> is noncommutative
case BinaryOperatorKind::BO_Cmp:
// Member access:
case BinaryOperatorKind::BO_PtrMemD:
case BinaryOperatorKind::BO_PtrMemI:
// Assignment:
case BinaryOperatorKind::BO_Assign:
case BinaryOperatorKind::BO_MulAssign:
case BinaryOperatorKind::BO_DivAssign:
case BinaryOperatorKind::BO_RemAssign:
case BinaryOperatorKind::BO_AddAssign:
case BinaryOperatorKind::BO_SubAssign:
case BinaryOperatorKind::BO_ShlAssign:
case BinaryOperatorKind::BO_ShrAssign:
case BinaryOperatorKind::BO_AndAssign:
case BinaryOperatorKind::BO_XorAssign:
case BinaryOperatorKind::BO_OrAssign:
return false;
}
return false;
}

/// Some operators are asymmetric and need to be flipped when swapping their
/// operands
/// @param[out] Opcode the opcode to potentially swap
/// If the opcode does not need to be swapped or is not swappable, does nothing
BinaryOperatorKind swapOperator(const BinaryOperatorKind Opcode) {
switch (Opcode) {
case BinaryOperatorKind::BO_LT:
return BinaryOperatorKind::BO_GT;

case BinaryOperatorKind::BO_GT:
return BinaryOperatorKind::BO_LT;

case BinaryOperatorKind::BO_LE:
return BinaryOperatorKind::BO_GE;

case BinaryOperatorKind::BO_GE:
return BinaryOperatorKind::BO_LE;

case BinaryOperatorKind::BO_Mul:
case BinaryOperatorKind::BO_Add:
case BinaryOperatorKind::BO_Cmp:
case BinaryOperatorKind::BO_EQ:
case BinaryOperatorKind::BO_NE:
case BinaryOperatorKind::BO_And:
case BinaryOperatorKind::BO_Xor:
case BinaryOperatorKind::BO_Or:
case BinaryOperatorKind::BO_LAnd:
case BinaryOperatorKind::BO_LOr:
case BinaryOperatorKind::BO_Comma:
case BinaryOperatorKind::BO_Div:
case BinaryOperatorKind::BO_Sub:
case BinaryOperatorKind::BO_Shl:
case BinaryOperatorKind::BO_Shr:
case BinaryOperatorKind::BO_Rem:
case BinaryOperatorKind::BO_PtrMemD:
case BinaryOperatorKind::BO_PtrMemI:
case BinaryOperatorKind::BO_Assign:
case BinaryOperatorKind::BO_MulAssign:
case BinaryOperatorKind::BO_DivAssign:
case BinaryOperatorKind::BO_RemAssign:
case BinaryOperatorKind::BO_AddAssign:
case BinaryOperatorKind::BO_SubAssign:
case BinaryOperatorKind::BO_ShlAssign:
case BinaryOperatorKind::BO_ShrAssign:
case BinaryOperatorKind::BO_AndAssign:
case BinaryOperatorKind::BO_XorAssign:
case BinaryOperatorKind::BO_OrAssign:
return Opcode;
}
llvm_unreachable("Unknown BinaryOperatorKind enum");
}

/// Swaps the operands to a binary operator
/// Before:
/// x != nullptr
/// ^ ^^^^^^^
/// After:
/// nullptr != x
class SwapBinaryOperands : public Tweak {
public:
const char *id() const final;

bool prepare(const Selection &Inputs) override;
Expected<Effect> apply(const Selection &Inputs) override;
std::string title() const override {
return llvm::formatv("Swap operands to {0}",
Op ? Op->getOpcodeStr() : "binary operator");
}
llvm::StringLiteral kind() const override {
return CodeAction::REFACTOR_KIND;
}
bool hidden() const override { return false; }

private:
const BinaryOperator *Op;
};

REGISTER_TWEAK(SwapBinaryOperands)

bool SwapBinaryOperands::prepare(const Selection &Inputs) {
for (const SelectionTree::Node *N = Inputs.ASTSelection.commonAncestor();
N && !Op; N = N->Parent) {
// Stop once we hit a block, e.g. a lambda in one of the operands.
// This makes sure that the selection point is in the 'scope' of the binary
// operator, not from somewhere inside a lambda for example
// (5 < [](){ ^return 1; })
if (llvm::isa_and_nonnull<CompoundStmt>(N->ASTNode.get<Stmt>()))
return false;
Op = dyn_cast_or_null<BinaryOperator>(N->ASTNode.get<Stmt>());
// If we hit upon a nonswappable binary operator, ignore and keep going
if (Op && !isOpSwappable(Op->getOpcode())) {
Op = nullptr;
}
}
return Op != nullptr;
}

Expected<Tweak::Effect> SwapBinaryOperands::apply(const Selection &Inputs) {
const auto &Ctx = Inputs.AST->getASTContext();
const auto &SrcMgr = Inputs.AST->getSourceManager();

const auto LHSRng = toHalfOpenFileRange(SrcMgr, Ctx.getLangOpts(),
Op->getLHS()->getSourceRange());
if (!LHSRng)
return error(
"Could not obtain range of the 'lhs' of the operator. Macros?");
const auto RHSRng = toHalfOpenFileRange(SrcMgr, Ctx.getLangOpts(),
Op->getRHS()->getSourceRange());
if (!RHSRng)
return error(
"Could not obtain range of the 'rhs' of the operator. Macros?");
const auto OpRng =
toHalfOpenFileRange(SrcMgr, Ctx.getLangOpts(), Op->getOperatorLoc());
if (!OpRng)
return error("Could not obtain range of the operator itself. Macros?");

const auto LHSCode = toSourceCode(SrcMgr, *LHSRng);
const auto RHSCode = toSourceCode(SrcMgr, *RHSRng);
const auto OperatorCode = toSourceCode(SrcMgr, *OpRng);

tooling::Replacements Result;
if (auto Err = Result.add(tooling::Replacement(
Ctx.getSourceManager(), LHSRng->getBegin(), LHSCode.size(), RHSCode)))
return std::move(Err);
if (auto Err = Result.add(tooling::Replacement(
Ctx.getSourceManager(), RHSRng->getBegin(), RHSCode.size(), LHSCode)))
return std::move(Err);
const auto SwappedOperator = swapOperator(Op->getOpcode());
if (auto Err = Result.add(tooling::Replacement(
Ctx.getSourceManager(), OpRng->getBegin(), OperatorCode.size(),
Op->getOpcodeStr(SwappedOperator))))
return std::move(Err);
return Effect::mainFileEdit(SrcMgr, std::move(Result));
}

} // namespace
} // namespace clangd
} // namespace clang
21 changes: 16 additions & 5 deletions clang-tools-extra/clangd/tool/ClangdMain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -242,13 +242,13 @@ opt<std::string> FallbackStyle{
init(clang::format::DefaultFallbackStyle),
};

opt<bool> EnableFunctionArgSnippets{
opt<int> EnableFunctionArgSnippets{
"function-arg-placeholders",
cat(Features),
desc("When disabled, completions contain only parentheses for "
"function calls. When enabled, completions also contain "
desc("When disabled (0), completions contain only parentheses for "
"function calls. When enabled (1), completions also contain "
"placeholders for method parameters"),
init(CodeCompleteOptions().EnableFunctionArgSnippets),
init(-1),
};

opt<CodeCompleteOptions::IncludeInsertion> HeaderInsertion{
Expand Down Expand Up @@ -650,6 +650,7 @@ class FlagsConfigProvider : public config::Provider {
std::optional<Config::CDBSearchSpec> CDBSearch;
std::optional<Config::ExternalIndexSpec> IndexSpec;
std::optional<Config::BackgroundPolicy> BGPolicy;
std::optional<Config::ArgumentListsPolicy> ArgumentLists;

// If --compile-commands-dir arg was invoked, check value and override
// default path.
Expand Down Expand Up @@ -694,13 +695,21 @@ class FlagsConfigProvider : public config::Provider {
BGPolicy = Config::BackgroundPolicy::Skip;
}

if (EnableFunctionArgSnippets >= 0) {
ArgumentLists = EnableFunctionArgSnippets
? Config::ArgumentListsPolicy::FullPlaceholders
: Config::ArgumentListsPolicy::Delimiters;
}

Frag = [=](const config::Params &, Config &C) {
if (CDBSearch)
C.CompileFlags.CDBSearch = *CDBSearch;
if (IndexSpec)
C.Index.External = *IndexSpec;
if (BGPolicy)
C.Index.Background = *BGPolicy;
if (ArgumentLists)
C.Completion.ArgumentLists = *ArgumentLists;
if (AllScopesCompletion.getNumOccurrences())
C.Completion.AllScopes = AllScopesCompletion;

Expand Down Expand Up @@ -916,9 +925,11 @@ clangd accepts flags on the commandline, and in the CLANGD_FLAGS environment var
Opts.CodeComplete.IncludeIndicator.Insert.clear();
Opts.CodeComplete.IncludeIndicator.NoInsert.clear();
}
Opts.CodeComplete.EnableFunctionArgSnippets = EnableFunctionArgSnippets;
Opts.CodeComplete.RunParser = CodeCompletionParse;
Opts.CodeComplete.RankingModel = RankingModel;
// FIXME: If we're using C++20 modules, force the lookup process to load
// external decls, since currently the index doesn't support C++20 modules.
Opts.CodeComplete.ForceLoadPreamble = ExperimentalModulesSupport;

RealThreadsafeFS TFS;
std::vector<std::unique_ptr<config::Provider>> ProviderStack;
Expand Down
1 change: 1 addition & 0 deletions clang-tools-extra/clangd/unittests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ add_unittest(ClangdUnitTests ClangdTests
tweaks/ScopifyEnumTests.cpp
tweaks/ShowSelectionTreeTests.cpp
tweaks/SpecialMembersTests.cpp
tweaks/SwapBinaryOperandsTests.cpp
tweaks/SwapIfBranchesTests.cpp
tweaks/TweakTesting.cpp
tweaks/TweakTests.cpp
Expand Down
16 changes: 16 additions & 0 deletions clang-tools-extra/clangd/unittests/ClangdLSPServerTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,22 @@ TEST_F(LSPTest, ClangTidyRename) {
EXPECT_EQ(Params, std::vector{llvm::json::Value(std::move(ExpectedEdit))});
}

TEST_F(LSPTest, ClangTidyCrash_Issue109367) {
// This test requires clang-tidy checks to be linked in.
if (!CLANGD_TIDY_CHECKS)
return;
Opts.ClangTidyProvider = [](tidy::ClangTidyOptions &ClangTidyOpts,
llvm::StringRef) {
ClangTidyOpts.Checks = {"-*,boost-use-ranges"};
};
// Check that registering the boost-use-ranges checker's matchers
// on two different threads does not cause a crash.
auto &Client = start();
Client.didOpen("a.cpp", "");
Client.didOpen("b.cpp", "");
Client.sync();
}

TEST_F(LSPTest, IncomingCalls) {
Annotations Code(R"cpp(
void calle^e(int);
Expand Down
25 changes: 23 additions & 2 deletions clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "ClangdServer.h"
#include "CodeComplete.h"
#include "Compiler.h"
#include "Config.h"
#include "Feature.h"
#include "Matchers.h"
#include "Protocol.h"
Expand Down Expand Up @@ -2595,10 +2596,10 @@ TEST(SignatureHelpTest, DynamicIndexDocumentation) {
ElementsAre(AllOf(sig("foo() -> int"), sigDoc("Member doc"))));
}

TEST(CompletionTest, CompletionFunctionArgsDisabled) {
TEST(CompletionTest, ArgumentListsPolicy) {
CodeCompleteOptions Opts;
Opts.EnableSnippets = true;
Opts.EnableFunctionArgSnippets = false;
Opts.ArgumentLists = Config::ArgumentListsPolicy::Delimiters;

{
auto Results = completions(
Expand Down Expand Up @@ -2670,6 +2671,26 @@ TEST(CompletionTest, CompletionFunctionArgsDisabled) {
EXPECT_THAT(Results.Completions, UnorderedElementsAre(AllOf(
named("FOO"), snippetSuffix("($0)"))));
}
{
Opts.ArgumentLists = Config::ArgumentListsPolicy::None;
auto Results = completions(
R"cpp(
void xfoo(int x, int y);
void f() { xfo^ })cpp",
{}, Opts);
EXPECT_THAT(Results.Completions,
UnorderedElementsAre(AllOf(named("xfoo"), snippetSuffix(""))));
}
{
Opts.ArgumentLists = Config::ArgumentListsPolicy::OpenDelimiter;
auto Results = completions(
R"cpp(
void xfoo(int x, int y);
void f() { xfo^ })cpp",
{}, Opts);
EXPECT_THAT(Results.Completions,
UnorderedElementsAre(AllOf(named("xfoo"), snippetSuffix("("))));
}
}

TEST(CompletionTest, SuggestOverrides) {
Expand Down
80 changes: 80 additions & 0 deletions clang-tools-extra/clangd/unittests/PrerequisiteModulesTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,86 @@ import A;
EXPECT_TRUE(D.isFromASTFile());
}

// An end to end test for code complete in modules
TEST_F(PrerequisiteModulesTests, CodeCompleteTest) {
MockDirectoryCompilationDatabase CDB(TestDir, FS);

CDB.addFile("A.cppm", R"cpp(
export module A;
export void printA();
)cpp");

llvm::StringLiteral UserContents = R"cpp(
import A;
void func() {
print^
}
)cpp";

CDB.addFile("Use.cpp", UserContents);
Annotations Test(UserContents);

ModulesBuilder Builder(CDB);

ParseInputs Use = getInputs("Use.cpp", CDB);
Use.ModulesManager = &Builder;

std::unique_ptr<CompilerInvocation> CI =
buildCompilerInvocation(Use, DiagConsumer);
EXPECT_TRUE(CI);

auto Preamble =
buildPreamble(getFullPath("Use.cpp"), *CI, Use, /*InMemory=*/true,
/*Callback=*/nullptr);
EXPECT_TRUE(Preamble);
EXPECT_TRUE(Preamble->RequiredModules);

auto Result = codeComplete(getFullPath("Use.cpp"), Test.point(),
Preamble.get(), Use, {});
EXPECT_FALSE(Result.Completions.empty());
EXPECT_EQ(Result.Completions[0].Name, "printA");
}

TEST_F(PrerequisiteModulesTests, SignatureHelpTest) {
MockDirectoryCompilationDatabase CDB(TestDir, FS);

CDB.addFile("A.cppm", R"cpp(
export module A;
export void printA(int a);
)cpp");

llvm::StringLiteral UserContents = R"cpp(
import A;
void func() {
printA(^);
}
)cpp";

CDB.addFile("Use.cpp", UserContents);
Annotations Test(UserContents);

ModulesBuilder Builder(CDB);

ParseInputs Use = getInputs("Use.cpp", CDB);
Use.ModulesManager = &Builder;

std::unique_ptr<CompilerInvocation> CI =
buildCompilerInvocation(Use, DiagConsumer);
EXPECT_TRUE(CI);

auto Preamble =
buildPreamble(getFullPath("Use.cpp"), *CI, Use, /*InMemory=*/true,
/*Callback=*/nullptr);
EXPECT_TRUE(Preamble);
EXPECT_TRUE(Preamble->RequiredModules);

auto Result = signatureHelp(getFullPath("Use.cpp"), Test.point(),
*Preamble.get(), Use, MarkupKind::PlainText);
EXPECT_FALSE(Result.signatures.empty());
EXPECT_EQ(Result.signatures[0].label, "printA(int a) -> void");
EXPECT_EQ(Result.signatures[0].parameters[0].labelString, "int a");
}

} // namespace
} // namespace clang::clangd

Expand Down
64 changes: 54 additions & 10 deletions clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -201,19 +201,63 @@ TEST_F(ShouldCollectSymbolTest, NoPrivateProtoSymbol) {
build(
R"(// Generated by the protocol buffer compiler. DO NOT EDIT!
namespace nx {
class Top_Level {};
class TopLevel {};
enum Kind {
KIND_OK,
Kind_Not_Ok,
enum Outer_Enum : int {
Outer_Enum_KIND1,
Outer_Enum_Kind_2,
};
bool Outer_Enum_IsValid(int);
class Outer_Inner {};
class Outer {
using Inner = Outer_Inner;
using Enum = Outer_Enum;
static constexpr Enum KIND1 = Outer_Enum_KIND1;
static constexpr Enum Kind_2 = Outer_Enum_Kind_2;
static bool Enum_IsValid(int);
int &x();
void set_x();
void _internal_set_x();
int &Outer_y();
};
enum Foo {
FOO_VAL1,
Foo_VAL2,
};
bool Foo_IsValid(int);
})");
EXPECT_TRUE(shouldCollect("nx::TopLevel"));
EXPECT_TRUE(shouldCollect("nx::Kind::KIND_OK"));
EXPECT_TRUE(shouldCollect("nx::Kind"));

EXPECT_FALSE(shouldCollect("nx::Top_Level"));
EXPECT_FALSE(shouldCollect("nx::Kind::Kind_Not_Ok"));
// Make sure all the mangled names for Outer::Enum is discarded.
EXPECT_FALSE(shouldCollect("nx::Outer_Enum"));
EXPECT_FALSE(shouldCollect("nx::Outer_Enum_KIND1"));
EXPECT_FALSE(shouldCollect("nx::Outer_Enum_Kind_2"));
EXPECT_FALSE(shouldCollect("nx::Outer_Enum_IsValid"));
// But nested aliases are preserved.
EXPECT_TRUE(shouldCollect("nx::Outer::Enum"));
EXPECT_TRUE(shouldCollect("nx::Outer::KIND1"));
EXPECT_TRUE(shouldCollect("nx::Outer::Kind_2"));
EXPECT_TRUE(shouldCollect("nx::Outer::Enum_IsValid"));

// Check for Outer::Inner.
EXPECT_FALSE(shouldCollect("nx::Outer_Inner"));
EXPECT_TRUE(shouldCollect("nx::Outer"));
EXPECT_TRUE(shouldCollect("nx::Outer::Inner"));

// Make sure field related information is preserved, unless it's explicitly
// marked with `_internal_`.
EXPECT_TRUE(shouldCollect("nx::Outer::x"));
EXPECT_TRUE(shouldCollect("nx::Outer::set_x"));
EXPECT_FALSE(shouldCollect("nx::Outer::_internal_set_x"));
EXPECT_TRUE(shouldCollect("nx::Outer::Outer_y"));

// Handling of a top-level enum
EXPECT_TRUE(shouldCollect("nx::Foo::FOO_VAL1"));
EXPECT_TRUE(shouldCollect("nx::FOO_VAL1"));
EXPECT_TRUE(shouldCollect("nx::Foo_IsValid"));
// Our heuristic goes wrong here, if the user has a nested name that starts
// with parent's name.
EXPECT_FALSE(shouldCollect("nx::Foo::Foo_VAL2"));
EXPECT_FALSE(shouldCollect("nx::Foo_VAL2"));
}

TEST_F(ShouldCollectSymbolTest, DoubleCheckProtoHeaderComment) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
//===-- SwapBinaryOperandsTests.cpp -----------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "TweakTesting.h"
#include "gmock/gmock-matchers.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"

namespace clang {
namespace clangd {
namespace {

TWEAK_TEST(SwapBinaryOperands);

TEST_F(SwapBinaryOperandsTest, Test) {
Context = Function;
EXPECT_EQ(apply("int *p = nullptr; bool c = ^p == nullptr;"),
"int *p = nullptr; bool c = nullptr == p;");
EXPECT_EQ(apply("int *p = nullptr; bool c = p ^== nullptr;"),
"int *p = nullptr; bool c = nullptr == p;");
EXPECT_EQ(apply("int x = 3; bool c = ^x >= 5;"),
"int x = 3; bool c = 5 <= x;");
EXPECT_EQ(apply("int x = 3; bool c = x >^= 5;"),
"int x = 3; bool c = 5 <= x;");
EXPECT_EQ(apply("int x = 3; bool c = x >=^ 5;"),
"int x = 3; bool c = 5 <= x;");
EXPECT_EQ(apply("int x = 3; bool c = x >=^ 5;"),
"int x = 3; bool c = 5 <= x;");
EXPECT_EQ(apply("int f(); int x = 3; bool c = x >=^ f();"),
"int f(); int x = 3; bool c = f() <= x;");
EXPECT_EQ(apply(R"cpp(
int f();
#define F f
int x = 3; bool c = x >=^ F();
)cpp"),
R"cpp(
int f();
#define F f
int x = 3; bool c = F() <= x;
)cpp");
EXPECT_EQ(apply(R"cpp(
int f();
#define F f()
int x = 3; bool c = x >=^ F;
)cpp"),
R"cpp(
int f();
#define F f()
int x = 3; bool c = F <= x;
)cpp");
EXPECT_EQ(apply(R"cpp(
int f(bool);
#define F(v) f(v)
int x = 0;
bool c = F(x^ < 5);
)cpp"),
R"cpp(
int f(bool);
#define F(v) f(v)
int x = 0;
bool c = F(5 > x);
)cpp");
ExtraArgs = {"-std=c++20"};
Context = CodeContext::File;
EXPECT_UNAVAILABLE(R"cpp(
namespace std {
struct strong_ordering {
int val;
static const strong_ordering less;
static const strong_ordering equivalent;
static const strong_ordering equal;
static const strong_ordering greater;
};
inline constexpr strong_ordering strong_ordering::less {-1};
inline constexpr strong_ordering strong_ordering::equivalent {0};
inline constexpr strong_ordering strong_ordering::equal {0};
inline constexpr strong_ordering strong_ordering::greater {1};
};
#define F(v) v
int x = 0;
auto c = F(5^ <=> x);
)cpp");
}

} // namespace
} // namespace clangd
} // namespace clang
46 changes: 34 additions & 12 deletions clang-tools-extra/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ Code completion
Code actions
^^^^^^^^^^^^

- Added `Swap operands` tweak for certain binary operators.

Signature help
^^^^^^^^^^^^^^

Expand All @@ -97,12 +99,28 @@ The improvements are...
Improvements to clang-tidy
--------------------------

- Improved :program:`clang-tidy`'s `--verify-config` flag by adding support for
the configuration options of the `Clang Static Analyzer Checks
<https://clang.llvm.org/docs/analyzer/checkers.html>`_.

- Improved :program:`run-clang-tidy.py` script. Fixed minor shutdown noise
happening on certain platforms when interrupting the script.

New checks
^^^^^^^^^^

- New :doc:`bugprone-bitwise-pointer-cast
<clang-tidy/checks/bugprone/bitwise-pointer-cast>` check.

Warns about code that tries to cast between pointers by means of
``std::bit_cast`` or ``memcpy``.

- New :doc:`bugprone-tagged-union-member-count
<clang-tidy/checks/bugprone/tagged-union-member-count>` check.

Gives warnings for tagged unions, where the number of tags is
different from the number of data members inside the union.

New check aliases
^^^^^^^^^^^^^^^^^

Expand Down Expand Up @@ -156,18 +174,18 @@ Changes in existing checks
<clang-tidy/checks/misc/definitions-in-headers>` check by rewording the
diagnostic note that suggests adding ``inline``.

- Improved :doc:`misc-unconventional-assign-operator
<clang-tidy/checks/misc/unconventional-assign-operator>` check to avoid
false positive for C++23 deducing this.

- Improved :doc:`modernize-avoid-c-arrays
<clang-tidy/checks/modernize/avoid-c-arrays>` check to suggest using ``std::span``
as a replacement for parameters of incomplete C array type in C++20 and
``std::array`` or ``std::vector`` before C++20.

- Improved :doc:`modernize-use-std-format
<clang-tidy/checks/modernize/use-std-format>` check to support replacing
member function calls too.

- Improved :doc:`misc-unconventional-assign-operator
<clang-tidy/checks/misc/unconventional-assign-operator>` check to avoid
false positive for C++23 deducing this.
- Improved :doc:`modernize-loop-convert
<clang-tidy/checks/modernize/loop-convert>` check to fix false positive when
using loop variable in initializer of lambda capture.

- Improved :doc:`modernize-min-max-use-initializer-list
<clang-tidy/checks/modernize/min-max-use-initializer-list>` check by fixing
Expand All @@ -178,15 +196,14 @@ Changes in existing checks
<clang-tidy/checks/modernize/use-nullptr>` check to also recognize
``NULL``/``__null`` (but not ``0``) when used with a templated type.

- Improved :doc:`modernize-use-std-format
<clang-tidy/checks/modernize/use-std-format>` check to support replacing
member function calls too.

- Improved :doc:`modernize-use-std-print
<clang-tidy/checks/modernize/use-std-print>` check to support replacing
member function calls too.

- Improved :doc:`readability-enum-initial-value
<clang-tidy/checks/readability/enum-initial-value>` check by only issuing
diagnostics for the definition of an ``enum``, and by fixing a typo in the
diagnostic.

- Improved :doc:`performance-avoid-endl
<clang-tidy/checks/performance/avoid-endl>` check to use ``std::endl`` as
placeholder when lexer cannot get source text.
Expand All @@ -195,6 +212,11 @@ Changes in existing checks
<clang-tidy/checks/readability/container-contains>` check to let it work on
any class that has a ``contains`` method.

- Improved :doc:`readability-enum-initial-value
<clang-tidy/checks/readability/enum-initial-value>` check by only issuing
diagnostics for the definition of an ``enum``, and by fixing a typo in the
diagnostic.

- Improved :doc:`readability-implicit-bool-conversion
<clang-tidy/checks/readability/implicit-bool-conversion>` check
by adding the option `UseUpperCaseLiteralSuffix` to select the
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
.. title:: clang-tidy - bugprone-bitwise-pointer-cast

bugprone-bitwise-pointer-cast
=============================

Warns about code that tries to cast between pointers by means of
``std::bit_cast`` or ``memcpy``.

The motivation is that ``std::bit_cast`` is advertised as the safe alternative
to type punning via ``reinterpret_cast`` in modern C++. However, one should not
blindly replace ``reinterpret_cast`` with ``std::bit_cast``, as follows:

.. code-block:: c++

int x{};
-float y = *reinterpret_cast<float*>(&x);
+float y = *std::bit_cast<float*>(&x);

The drop-in replacement behaves exactly the same as ``reinterpret_cast``, and
Undefined Behavior is still invoked. ``std::bit_cast`` is copying the bytes of
the input pointer, not the pointee, into an output pointer of a different type,
which may violate the strict aliasing rules. However, simply looking at the
code, it looks "safe", because it uses ``std::bit_cast`` which is advertised as
safe.

The solution to safe type punning is to apply ``std::bit_cast`` on value types,
not on pointer types:

.. code-block:: c++

int x{};
float y = std::bit_cast<float>(x);

This way, the bytes of the input object are copied into the output object, which
is much safer. Do note that Undefined Behavior can still occur, if there is no
value of type ``To`` corresponding to the value representation produced.
Compilers may be able to optimize this copy and generate identical assembly to
the original ``reinterpret_cast`` version.

Code before C++20 may backport ``std::bit_cast`` by means of ``memcpy``, or
simply call ``memcpy`` directly, which is equally problematic. This is also
detected by this check:

.. code-block:: c++

int* x{};
float* y{};
std::memcpy(&y, &x, sizeof(x));

Alternatively, if a cast between pointers is truly wanted, ``reinterpret_cast``
should be used, to clearly convey the intent and enable warnings from compilers
and linters, which should be addressed accordingly.
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
.. title:: clang-tidy - bugprone-tagged-union-member-count

bugprone-tagged-union-member-count
==================================

Gives warnings for tagged unions, where the number of tags is
different from the number of data members inside the union.

A struct or a class is considered to be a tagged union if it has
exactly one union data member and exactly one enum data member and
any number of other data members that are neither unions or enums.

Example:

.. code-block:: c++

enum Tags {
Tag1,
Tag2,
};

struct TaggedUnion { // warning: tagged union has more data members (3) than tags (2)
enum Tags Kind;
union {
int I;
float F;
char *Str;
} Data;
};
How enum constants are counted
------------------------------

The main complicating factor when counting the number of enum constants is that
some of them might be auxiliary values that purposefully don't have a corresponding union
data member and are used for something else. For example the last enum constant
sometimes explicitly "points to" the last declared valid enum constant or
tracks how many enum constants have been declared.

For an illustration:

.. code-block:: c++

enum TagWithLast {
Tag1 = 0,
Tag2 = 1,
Tag3 = 2,
LastTag = 2
};

enum TagWithCounter {
Tag1, // is 0
Tag2, // is 1
Tag3, // is 2
TagCount, // is 3
};

The check counts the number of distinct values among the enum constants and not the enum
constants themselves. This way the enum constants that are essentially just aliases of other
enum constants are not included in the final count.

Handling of counting enum constants (ones like :code:`TagCount` in the previous code example)
is done by decreasing the number of enum values by one if the name of the last enum constant
starts with a prefix or ends with a suffix specified in :option:`CountingEnumPrefixes`,
:option:`CountingEnumSuffixes` and it's value is one less than the total number of distinct
values in the enum.

When the final count is adjusted based on this heuristic then a diagnostic note is emitted
that shows which enum constant matched the criteria.

The heuristic can be disabled entirely (:option:`EnableCountingEnumHeuristic`) or
configured to follow your naming convention (:option:`CountingEnumPrefixes`, :option:`CountingEnumSuffixes`).
The strings specified in :option:`CountingEnumPrefixes`, :option:`CountingEnumSuffixes` are matched
case insensitively.

Example counts:

.. code-block:: c++

// Enum count is 3, because the value 2 is counted only once
enum TagWithLast {
Tag1 = 0,
Tag2 = 1,
Tag3 = 2,
LastTag = 2
};

// Enum count is 3, because TagCount is heuristically excluded
enum TagWithCounter {
Tag1, // is 0
Tag2, // is 1
Tag3, // is 2
TagCount, // is 3
};


Options
-------

.. option:: EnableCountingEnumHeuristic

This option enables or disables the counting enum heuristic.
It uses the prefixes and suffixes specified in the options
:option:`CountingEnumPrefixes`, :option:`CountingEnumSuffixes` to find counting enum constants by
using them for prefix and suffix matching.

This option is enabled by default.

When :option:`EnableCountingEnumHeuristic` is `false`:

.. code-block:: c++

enum TagWithCounter {
Tag1,
Tag2,
Tag3,
TagCount,
};

struct TaggedUnion {
TagWithCounter Kind;
union {
int A;
long B;
char *Str;
float F;
} Data;
};
When :option:`EnableCountingEnumHeuristic` is `true`:

.. code-block:: c++

enum TagWithCounter {
Tag1,
Tag2,
Tag3,
TagCount,
};

struct TaggedUnion { // warning: tagged union has more data members (4) than tags (3)
TagWithCounter Kind;
union {
int A;
long B;
char *Str;
float F;
} Data;
};
.. option:: CountingEnumPrefixes

See :option:`CountingEnumSuffixes` below.

.. option:: CountingEnumSuffixes

CountingEnumPrefixes and CountingEnumSuffixes are lists of semicolon
separated strings that are used to search for possible counting enum constants.
These strings are matched case insensitively as prefixes and suffixes
respectively on the names of the enum constants.
If :option:`EnableCountingEnumHeuristic` is `false` then these options do nothing.

The default value of :option:`CountingEnumSuffixes` is `count` and of
:option:`CountingEnumPrefixes` is the empty string.

When :option:`EnableCountingEnumHeuristic` is `true` and :option:`CountingEnumSuffixes`
is `count;size`:

.. code-block:: c++

enum TagWithCounterCount {
Tag1,
Tag2,
Tag3,
TagCount,
};

struct TaggedUnionCount { // warning: tagged union has more data members (4) than tags (3)
TagWithCounterCount Kind;
union {
int A;
long B;
char *Str;
float F;
} Data;
};
enum TagWithCounterSize {
Tag11,
Tag22,
Tag33,
TagSize,
};

struct TaggedUnionSize { // warning: tagged union has more data members (4) than tags (3)
TagWithCounterSize Kind;
union {
int A;
long B;
char *Str;
float F;
} Data;
};
When :option:`EnableCountingEnumHeuristic` is `true` and :option:`CountingEnumPrefixes` is `maxsize;last_`

.. code-block:: c++

enum TagWithCounterLast {
Tag1,
Tag2,
Tag3,
last_tag,
};

struct TaggedUnionLast { // warning: tagged union has more data members (4) than tags (3)
TagWithCounterLast tag;
union {
int I;
short S;
char *C;
float F;
} Data;
};
enum TagWithCounterMaxSize {
Tag1,
Tag2,
Tag3,
MaxSizeTag,
};

struct TaggedUnionMaxSize { // warning: tagged union has more data members (4) than tags (3)
TagWithCounterMaxSize tag;
union {
int I;
short S;
char *C;
float F;
} Data;
};
.. option:: StrictMode

When enabled, the check will also give a warning, when the number of tags
is greater than the number of union data members.

This option is disabled by default.

When :option:`StrictMode` is `false`:

.. code-block:: c++

struct TaggedUnion {
enum {
Tag1,
Tag2,
Tag3,
} Tags;
union {
int I;
float F;
} Data;
};

When :option:`StrictMode` is `true`:

.. code-block:: c++

struct TaggedUnion { // warning: tagged union has fewer data members (2) than tags (3)
enum {
Tag1,
Tag2,
Tag3,
} Tags;
union {
int I;
float F;
} Data;
};
2 changes: 2 additions & 0 deletions clang-tools-extra/docs/clang-tidy/checks/list.rst
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ Clang-Tidy Checks
:doc:`bugprone-assert-side-effect <bugprone/assert-side-effect>`,
:doc:`bugprone-assignment-in-if-condition <bugprone/assignment-in-if-condition>`,
:doc:`bugprone-bad-signal-to-kill-thread <bugprone/bad-signal-to-kill-thread>`,
:doc:`bugprone-bitwise-pointer-cast <bugprone/bitwise-pointer-cast>`,
:doc:`bugprone-bool-pointer-implicit-conversion <bugprone/bool-pointer-implicit-conversion>`, "Yes"
:doc:`bugprone-branch-clone <bugprone/branch-clone>`,
:doc:`bugprone-casting-through-void <bugprone/casting-through-void>`,
Expand Down Expand Up @@ -145,6 +146,7 @@ Clang-Tidy Checks
:doc:`bugprone-suspicious-stringview-data-usage <bugprone/suspicious-stringview-data-usage>`,
:doc:`bugprone-swapped-arguments <bugprone/swapped-arguments>`, "Yes"
:doc:`bugprone-switch-missing-default-case <bugprone/switch-missing-default-case>`,
:doc:`bugprone-tagged-union-member-count <bugprone/tagged-union-member-count>`,
:doc:`bugprone-terminating-continue <bugprone/terminating-continue>`, "Yes"
:doc:`bugprone-throw-keyword-missing <bugprone/throw-keyword-missing>`,
:doc:`bugprone-too-small-loop-variable <bugprone/too-small-loop-variable>`,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// RUN: %check_clang_tidy -std=c++20 %s bugprone-bitwise-pointer-cast %t

void memcpy(void* to, void* dst, unsigned long long size)
{
// Dummy implementation for the purpose of the test
}

namespace std
{
template <typename To, typename From>
To bit_cast(From from)
{
// Dummy implementation for the purpose of the test
To to{};
return to;
}

using ::memcpy;
}

void pointer2pointer()
{
int x{};
float bad = *std::bit_cast<float*>(&x); // UB, but looks safe due to std::bit_cast
// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: do not use 'std::bit_cast' to cast between pointers [bugprone-bitwise-pointer-cast]
float good = std::bit_cast<float>(x); // Well-defined

using IntPtr = int*;
using FloatPtr = float*;
IntPtr x2{};
float bad2 = *std::bit_cast<FloatPtr>(x2);
// CHECK-MESSAGES: :[[@LINE-1]]:17: warning: do not use 'std::bit_cast' to cast between pointers [bugprone-bitwise-pointer-cast]
}

void pointer2pointer_memcpy()
{
int x{};
int* px{};
float y{};
float* py{};

memcpy(&py, &px, sizeof(px));
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not use 'memcpy' to cast between pointers [bugprone-bitwise-pointer-cast]
std::memcpy(&py, &px, sizeof(px));
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not use 'memcpy' to cast between pointers [bugprone-bitwise-pointer-cast]

std::memcpy(&y, &x, sizeof(x));
}

// Pointer-integer conversions are allowed by this check
void int2pointer()
{
unsigned long long addr{};
float* p = std::bit_cast<float*>(addr);
std::memcpy(&p, &addr, sizeof(addr));
}

void pointer2int()
{
float* p{};
auto addr = std::bit_cast<unsigned long long>(p);
std::memcpy(&addr, &p, sizeof(p));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// RUN: %check_clang_tidy %s bugprone-bitwise-pointer-cast %t

void memcpy(void* to, void* dst, unsigned long long size)
{
// Dummy implementation for the purpose of the test
}

namespace std
{
using ::memcpy;
}

void pointer2pointer()
{
int x{};
int* px{};
float y{};
float* py{};

memcpy(&py, &px, sizeof(px));
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not use 'memcpy' to cast between pointers [bugprone-bitwise-pointer-cast]
std::memcpy(&py, &px, sizeof(px));
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: do not use 'memcpy' to cast between pointers [bugprone-bitwise-pointer-cast]

std::memcpy(&y, &x, sizeof(x));
}

// Pointer-integer conversions are allowed by this check
void int2pointer()
{
unsigned long long addr{};
float* p{};
std::memcpy(&p, &addr, sizeof(addr));
}

void pointer2int()
{
unsigned long long addr{};
float* p{};
std::memcpy(&addr, &p, sizeof(p));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// RUN: %check_clang_tidy %s bugprone-tagged-union-member-count %t \
// RUN: -config='{CheckOptions: { \
// RUN: bugprone-tagged-union-member-count.EnableCountingEnumHeuristic: false, \
// RUN: bugprone-tagged-union-member-count.CountingEnumSuffixes: "count", \
// RUN: bugprone-tagged-union-member-count.CountingEnumPrefixes: "last", \
// RUN: }}'

// Warn when the heuristic is disabled and a suffix or a prefix is set explicitly.

// CHECK-MESSAGES: warning: bugprone-tagged-union-member-count: Counting enum heuristic is disabled but CountingEnumPrefixes is set
// CHECK-MESSAGES: warning: bugprone-tagged-union-member-count: Counting enum heuristic is disabled but CountingEnumSuffixes is set
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// RUN: %check_clang_tidy -std=c++98-or-later %s bugprone-tagged-union-member-count %t \
// RUN: -config='{CheckOptions: { \
// RUN: bugprone-tagged-union-member-count.StrictMode: true, \
// RUN: bugprone-tagged-union-member-count.EnableCountingEnumHeuristic: false, \
// RUN: }}' --

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has fewer data members (3) than tags (4)
struct IncorrectBecauseHeuristicIsDisabledPrefixCase {
enum {
tags11,
tags22,
tags33,
lasttag,
} Tags;
union {
char A;
short B;
int C;
} Data;
};

struct CorrectBecauseHeuristicIsDisabledPrefixCase { // No warnings expected
enum {
tags1,
tags2,
tags3,
lasttags,
} Tags;
union {
char A;
short B;
int C;
long D;
} Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has fewer data members (3) than tags (4)
struct IncorrectBecauseHeuristicIsDisabledSuffixCase {
enum {
tags11,
tags22,
tags33,
tags_count,
} Tags;
union {
char A;
short B;
int C;
} Data;
};

struct CorrectBecauseHeuristicIsDisabledSuffixCase { // No warnings expected
enum {
tags1,
tags2,
tags3,
tags_count,
} Tags;
union {
char A;
short B;
int C;
long D;
} Data;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// RUN: %check_clang_tidy -std=c++98-or-later %s bugprone-tagged-union-member-count %t \
// RUN: -config='{CheckOptions: { \
// RUN: bugprone-tagged-union-member-count.StrictMode: false, \
// RUN: bugprone-tagged-union-member-count.EnableCountingEnumHeuristic: true, \
// RUN: bugprone-tagged-union-member-count.CountingEnumSuffixes: "count", \
// RUN: bugprone-tagged-union-member-count.CountingEnumPrefixes: "last", \
// RUN: }}' --

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (3) than tags (2)
struct IncorrectBecauseHeuristicIsEnabledPrefixCase {
enum {
tags1,
tags2,
lasttag,
} Tags;
union {
char A;
short B;
int C;
} Data;
};

struct CorrectBecauseHeuristicIsEnabledPrefixCase { // No warnings expected
enum {
tags1,
tags2,
tags3,
lasttag,
} Tags;
union {
int A;
int B;
int C;
} Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (3) than tags (2)
struct IncorrectBecauseHeuristicIsEnabledSuffixCase {
enum {
tags1,
tags2,
tags_count,
} Tags;
union {
char A;
short B;
int C;
} Data;
};

struct CorrectBecauseHeuristicIsEnabledSuffixCase { // No warnings expected
enum {
tags1,
tags2,
tags3,
tags_count,
} Tags;
union {
int A;
int B;
int C;
} Data;
};

union Union4 {
short *Shorts;
double *Doubles;
int *Ints;
float *Floats;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct CountingEnumCaseInsensitivityTest1 {
enum {
node_type_loop,
node_type_branch,
node_type_function,
node_type_count,
} Kind;
union Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct CountingEnumCaseInsensitivityTest2 {
enum {
NODE_TYPE_LOOP,
NODE_TYPE_BRANCH,
NODE_TYPE_FUNCTION,
NODE_TYPE_COUNT,
} Kind;
union Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TagWhereCountingEnumIsAliased {
enum {
tag_alias_counter1 = 1,
tag_alias_counter2 = 2,
tag_alias_counter3 = 3,
tag_alias_other_count = 3,
} Kind;
union {
char C;
short S;
int I;
long L;
} Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (2)
struct TagWithCountingEnumButOtherValueIsAliased {
enum {
tag_alias_other1 = 1,
tag_alias_other2 = 1,
tag_alias_other3 = 3,
tag_alias_other_count = 2,
} Kind;
union {
char C;
short S;
int I;
long L;
} Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TagWhereCounterIsTheSmallest {
enum {
tag_large1 = 1000,
tag_large2 = 1001,
tag_large3 = 1002,
tag_large_count = 3,
} Kind;
union {
char C;
short S;
int I;
long L;
} Data;
};

// No warnings expected, only the last enum constant can be a counting enum constant
struct TagWhereCounterLikeNameIsNotLast {
enum {
kind_count,
kind2,
last_kind1,
kind3,
} Kind;
union {
char C;
short S;
int I;
long L;
} Data;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// RUN: %check_clang_tidy -std=c++98-or-later %s bugprone-tagged-union-member-count %t \
// RUN: -config='{CheckOptions: { \
// RUN: bugprone-tagged-union-member-count.StrictMode: false, \
// RUN: bugprone-tagged-union-member-count.EnableCountingEnumHeuristic: true, \
// RUN: bugprone-tagged-union-member-count.CountingEnumSuffixes: "count", \
// RUN: bugprone-tagged-union-member-count.CountingEnumPrefixes: "last", \
// RUN: }}' --

union Union3 {
short *Shorts;
int *Ints;
float *Floats;
};

union Union4 {
short *Shorts;
double *Doubles;
int *Ints;
float *Floats;
};

// The heuristic only considers the last enum constant
// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionPrefixAndSuffixMatch {
enum {
tags1,
tags2,
tagscount,
lasttags
} Kind;
Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (3) than tags (2)
struct TaggedUnionOnlyPrefixMatch {
enum {
prefixtag1,
prefixtag2,
lastprefixtag
} Kind;
Union3 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (3) than tags (2)
struct TaggedUnionOnlySuffixMatch {
enum {
suffixtag1,
suffixtag2,
suffixtagcount
} Kind;
Union3 Data;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// RUN: %check_clang_tidy -std=c++98-or-later %s bugprone-tagged-union-member-count %t \
// RUN: -config='{CheckOptions: { \
// RUN: bugprone-tagged-union-member-count.StrictMode: false, \
// RUN: bugprone-tagged-union-member-count.EnableCountingEnumHeuristic: true, \
// RUN: bugprone-tagged-union-member-count.CountingEnumPrefixes: "maxsize;last", \
// RUN: }}' --

union Union4 {
short *Shorts;
double *Doubles;
int *Ints;
float *Floats;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionWithMaxsizeAsCounterPrefix {
enum {
twc1,
twc2,
twc3,
maxsizetwc,
} Kind;
Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionWithLastAsCounterPrefix {
enum {
twc11,
twc22,
twc33,
lasttwc,
} Kind;
Union4 Data;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// RUN: %check_clang_tidy -std=c++98-or-later %s bugprone-tagged-union-member-count %t \
// RUN: -config='{CheckOptions: { \
// RUN: bugprone-tagged-union-member-count.StrictMode: false, \
// RUN: bugprone-tagged-union-member-count.EnableCountingEnumHeuristic: true, \
// RUN: bugprone-tagged-union-member-count.CountingEnumSuffixes: "count;size", \
// RUN: }}' --

typedef union Union4 {
short *Shorts;
double *Doubles;
int *Ints;
float *Floats;
} union4;

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionWithCounterCountSuffix {
enum {
twc1,
twc2,
twc3,
twc_count,
} Kind;
union Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionWithCounterSizeSuffix {
enum {
twc11,
twc22,
twc33,
twc_size,
} Kind;
union Union4 Data;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// RUN: %check_clang_tidy -std=c++98-or-later %s bugprone-tagged-union-member-count %t \
// RUN: -config='{CheckOptions: { \
// RUN: bugprone-tagged-union-member-count.StrictMode: false, \
// RUN: }}' --

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (2) than tags (1)
struct Incorrect {
enum {
tags1,
} Tags;
union {
char A;
short B;
} Data;
};

struct CorrectBecauseStrictModeIsDisabled { // No warnings expected
enum {
tags1,
tags2,
tags3,
} Tags;
union {
char A;
short B;
} Data;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// RUN: %check_clang_tidy -std=c++98-or-later %s bugprone-tagged-union-member-count %t \
// RUN: -config='{CheckOptions: { \
// RUN: bugprone-tagged-union-member-count.StrictMode: true, \
// RUN: }}' --

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has fewer data members (2) than tags (3)
struct IncorrectBecauseStrictmodeIsEnabled {
enum {
tags1,
tags2,
tags3,
} Tags;
union {
char A;
short B;
} Data;
};

struct Correct { // No warnings expected
enum {
tags1,
tags2,
tags3,
} Tags;
union {
char A;
short B;
int C;
} Data;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// RUN: %check_clang_tidy %s bugprone-tagged-union-member-count %t

typedef enum Tags3 {
tags3_1,
tags3_2,
tags3_3,
} Tags3;

typedef enum Tags4 {
tags4_1,
tags4_2,
tags4_3,
tags4_4,
} Tags4;

typedef union Union3 {
short *Shorts;
int *Ints;
float *Floats;
} Union3;

typedef union Union4 {
short *Shorts;
double *Doubles;
int *Ints;
float *Floats;
} Union4;

// It is not obvious which enum is the tag for the union.
struct maybeTaggedUnion1 { // No warnings expected.
enum Tags3 TagA;
enum Tags4 TagB;
union Union4 Data;
};

// It is not obvious which union does the tag belong to.
struct maybeTaggedUnion2 { // No warnings expected.
enum Tags3 Tag;
union Union3 DataB;
union Union3 DataA;
};

// It is not obvious which union does the tag belong to.
struct maybeTaggedUnion3 { // No warnings expected.
enum Tags3 Tag;
union {
int I1;
int I2;
int I3;
};
union {
float F1;
float F2;
float F3;
};
};

// No warnings expected, because LastATag is just an alias
struct TaggedUnionWithAliasedEnumConstant {
enum {
ATag1,
ATag2,
ATag3,
LastATag = ATag3,
} Tag;
union {
float F;
int *Ints;
char Key[8];
} Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionStructWithPredefinedTagAndPredefinedUnion {
enum Tags3 Tag;
union Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionStructWithPredefinedTagAndInlineUnion {
enum Tags3 Tag;
union {
int *Ints;
char Characters[13];
struct {
double Re;
double Im;
} Complex;
long L;
} Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionStructWithInlineTagAndPredefinedUnion {
enum {
TaggedUnion7tag1,
TaggedUnion7tag2,
TaggedUnion7tag3,
} Tag;
union Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionStructWithInlineTagAndInlineUnion {
enum {
TaggedUnion8tag1,
TaggedUnion8tag2,
TaggedUnion8tag3,
} Tag;
union {
int *Ints;
char Characters[13];
struct {
double Re;
double Im;
} Complex;
long L;
} Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionStructNesting {
enum Tags3 Tag;
union {
float F;
int I;
long L;
// CHECK-MESSAGES: :[[@LINE+1]]:12: warning: tagged union has more data members (4) than tags (3)
struct innerdecl {
enum Tags3 Tag;
union Union4 Data;
} Inner;
} Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionStructWithTypedefedTagAndTypedefedUnion {
Tags3 Tag;
Union4 Data;
};

#define DECLARE_TAGGED_UNION_STRUCT(Tag, Union, Name)\
struct Name {\
Tag Kind;\
Union Data;\
}

// CHECK-MESSAGES: :[[@LINE+1]]:44: warning: tagged union has more data members (4) than tags (3)
DECLARE_TAGGED_UNION_STRUCT(Tags3, Union4, TaggedUnionStructFromMacro);
Original file line number Diff line number Diff line change
@@ -0,0 +1,310 @@
// RUN: %check_clang_tidy -std=c++98-or-later %s bugprone-tagged-union-member-count %t
// Test check with C++ features

typedef enum Tags3 {
tags3_1,
tags3_2,
tags3_3,
} Tags3;

typedef enum Tags4 {
tags4_1,
tags4_2,
tags4_3,
tags4_4,
} Tags4;

enum class Classtags3 {
classtags3_1,
classtags3_2,
classtags3_3,
};

enum class Typedtags3 : unsigned int {
typedtags3_1,
typedtags3_2,
typedtags3_3,
};

typedef union Union3 {
short *Shorts;
int *Ints;
float *Floats;
} Union3;

typedef union Union4 {
short *Shorts;
double *Doubles;
int *Ints;
float *Floats;
} Union4;

// It is not obvious which enum is the tag for the union.
class MaybeTaggedUnion1 { // No warnings expected.
enum Tags3 TagA;
enum Tags4 TagB;
union Union4 Data;
};

// It is not obvious which union does the tag belong to.
class MaybeTaggedUnion2 { // No warnings expected.
enum Tags3 Tag;
union Union3 DataB;
union Union3 DataA;
};

// It is not obvious which union does the tag belong to.
class MaybeTaggedUnion3 { // No warnings expected.
enum Tags3 Tag;
union {
int I1;
int I2;
int I3;
};
union {
float F1;
float F2;
float F3;
};
};

// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: tagged union has more data members (4) than tags (3)
class TaggedUnionClassPredefinedTagAndPredefinedUnion {
enum Tags3 Tag;
union Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: tagged union has more data members (4) than tags (3)
class TaggedUnionClassPredefinedTagAndInlineUnion {
enum Tags3 Tag;
union {
int *Ints;
char Characters[13];
class {
double Re;
double Im;
} Complex;
long L;
} Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: tagged union has more data members (4) than tags (3)
class TaggedUnionClassInlineTagAndPredefinedUnion {
enum {
tag1,
tag2,
tag3,
} Tag;
union Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: tagged union has more data members (4) than tags (3)
class TaggedUnionClassInlineTagAndInlineUnion {
enum {
tag1,
tag2,
tag3,
} Tag;
union {
int *Ints;
char Characters[13];
class {
double Re;
double Im;
} Complex;
long L;
} Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: tagged union has more data members (4) than tags (3)
class TaggedUnionClassWithNestedTaggedUnionClass {
enum Tags3 Tag;
union {
float F;
int I;
long L;
// CHECK-MESSAGES: :[[@LINE+1]]:11: warning: tagged union has more data members (4) than tags (3)
class Innerdecl {
enum Tags3 Tag;
union Union4 Data;
} Inner;
} Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: tagged union has more data members (4) than tags (3)
class TaggedUnionClassWithTypedefedTag {
Tags3 Tag;
Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionStructWithEnumClass {
enum Classtags3 Tag;
Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: tagged union has more data members (4) than tags (3)
class TaggedUnionClasswithEnumClass {
enum Classtags3 Tag;
Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionStructWithTypedEnum {
Typedtags3 Tag;
Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: tagged union has more data members (4) than tags (3)
class TaggedUnionClassWithTypedEnum {
Typedtags3 Tag;
Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct AnonymousTaggedUnionStruct {
Tags3 Tag;
union {
char A;
short B;
int C;
long D;
};
};

// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: tagged union has more data members (4) than tags (3)
class TaggedUnionClassWithAnonymousUnion {
Tags3 Tag;
union {
char A;
short B;
int C;
long D;
};
};

namespace testnamespace {

enum Tags3 {
tags3_1,
tags3_2,
tags3_3,
};

union Union4 {
short *Shorts;
double *Doubles;
int *Ints;
float *Floats;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionStructInNamespace {
Tags3 Tags;
Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: tagged union has more data members (4) than tags (3)
class TaggedUnionClassInNamespace {
Tags3 Tags;
Union4 Data;
};

} // namespace testnamespace

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionStructWithNamespacedTagAndUnion {
testnamespace::Tags3 Tags;
testnamespace::Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:7: warning: tagged union has more data members (4) than tags (3)
class TaggedUnionClassWithNamespacedTagAndUnion {
testnamespace::Tags3 Tags;
testnamespace::Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+2]]:8: warning: tagged union has more data members (4) than tags (3)
template <typename Tag, typename Union>
struct TemplatedStructWithNamespacedTagAndUnion {
Tag Kind;
Union Data;
};

TemplatedStructWithNamespacedTagAndUnion<testnamespace::Union4, testnamespace::Tags3> TemplatedStruct3;

// CHECK-MESSAGES: :[[@LINE+2]]:7: warning: tagged union has more data members (4) than tags (3)
template <typename Tag, typename Union>
class TemplatedClassWithNamespacedTagAndUnion {
Tag Kind;
Union Data;
};

TemplatedClassWithNamespacedTagAndUnion<testnamespace::Union4, testnamespace::Tags3> TemplatedClass3;

// CHECK-MESSAGES: :[[@LINE+2]]:8: warning: tagged union has more data members (4) than tags (3)
template <typename Tag, typename Union>
struct TemplatedStruct {
Tag Kind;
Union Data;
};

TemplatedStruct<Tags3, Union3> TemplatedStruct1; // No warning expected
TemplatedStruct<Tags3, Union4> TemplatedStruct2;

// CHECK-MESSAGES: :[[@LINE+2]]:7: warning: tagged union has more data members (4) than tags (3)
template <typename Tag, typename Union>
class TemplatedClass {
Tag Kind;
Union Data;
};

TemplatedClass<Tags3, Union3> TemplatedClass1; // No warning expected
TemplatedClass<Tags3, Union4> TemplatedClass2;

// CHECK-MESSAGES: :[[@LINE+2]]:8: warning: tagged union has more data members (4) than tags (3)
template <typename T>
struct TemplatedStructButTaggedUnionPartIsNotTemplated {
Tags3 Kind;
Union4 Data;
T SomethingElse;
};

// CHECK-MESSAGES: :[[@LINE+2]]:7: warning: tagged union has more data members (4) than tags (3)
template <typename T>
class TemplatedClassButTaggedUnionPartIsNotTemplated {
Tags3 Kind;
Union4 Data;
T SomethingElse;
};

#define DECLARE_TAGGED_UNION_STRUCT(Tag, Union, Name)\
struct Name {\
Tag Kind;\
Union Data;\
}

// CHECK-MESSAGES: :[[@LINE+1]]:44: warning: tagged union has more data members (4) than tags (3)
DECLARE_TAGGED_UNION_STRUCT(Tags3, Union4, TaggedUnionStructFromMacro);

#define DECLARE_TAGGED_UNION_CLASS(Tag, Union, Name)\
class Name {\
Tag Kind;\
Union Data;\
}

// CHECK-MESSAGES: :[[@LINE+1]]:43: warning: tagged union has more data members (4) than tags (3)
DECLARE_TAGGED_UNION_CLASS(Tags3, Union4, TaggedUnionClassFromMacro);

// Lambdas implicitly compile down to an unnamed CXXRecordDecl and if they have captures,
// then those become unnamed fields.
void DoNotMatchLambdas() {
enum {
A
} e;
union {
long A;
char B;
} u;
auto L = [e, u] () {};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// RUN: %check_clang_tidy %s bugprone-tagged-union-member-count %t

typedef enum Tags3 {
tags3_1,
tags3_2,
tags3_3,
} Tags3;

typedef enum Tags4 {
tags4_1,
tags4_2,
tags4_3,
tags4_4,
} Tags4;

typedef union Union3 {
short *Shorts;
int *Ints;
float *Floats;
} Union3;

typedef union Union4 {
short *Shorts;
double *Doubles;
int *Ints;
float *Floats;
} Union4;

// It is not obvious which enum is the tag for the union.
struct maybeTaggedUnion1 { // No warnings expected.
enum Tags3 TagA;
enum Tags4 TagB;
union Union4 Data;
};

// It is not obvious which union does the tag belong to.
struct maybeTaggedUnion2 { // No warnings expected.
enum Tags3 Tag;
union Union3 DataB;
union Union3 DataA;
};

// It is not obvious which union does the tag belong to.
struct maybeTaggedUnion3 { // No warnings expected.
enum Tags3 Tag;
union {
int I1;
int I2;
int I3;
};
union {
float F1;
float F2;
float F3;
};
};

// No warnings expected, because LastATag is just an alias
struct TaggedUnionWithAliasedEnumConstant {
enum {
ATag1,
ATag2,
ATag3,
LastATag = ATag3,
} Tag;
union {
float F;
int *Ints;
char Key[8];
} Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionStructWithPredefinedTagAndPredefinedUnion {
enum Tags3 Tag;
union Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionStructWithPredefinedTagAndInlineUnion {
enum Tags3 Tag;
union {
int *Ints;
char Characters[13];
struct {
double Re;
double Im;
} Complex;
long L;
} Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionStructWithInlineTagAndPredefinedUnion {
enum {
TaggedUnion7tag1,
TaggedUnion7tag2,
TaggedUnion7tag3,
} Tag;
union Union4 Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionStructWithInlineTagAndInlineUnion {
enum {
TaggedUnion8tag1,
TaggedUnion8tag2,
TaggedUnion8tag3,
} Tag;
union {
int *Ints;
char Characters[13];
struct {
double Re;
double Im;
} Complex;
long L;
} Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionStructNesting {
enum Tags3 Tag;
union {
float F;
int I;
long L;
// CHECK-MESSAGES: :[[@LINE+1]]:12: warning: tagged union has more data members (4) than tags (3)
struct innerdecl {
enum Tags3 Tag;
union Union4 Data;
} Inner;
} Data;
};

// CHECK-MESSAGES: :[[@LINE+1]]:8: warning: tagged union has more data members (4) than tags (3)
struct TaggedUnionStructWithTypedefedTagAndTypedefedUnion {
Tags3 Tag;
Union4 Data;
};

#define DECLARE_TAGGED_UNION_STRUCT(Tag, Union, Name)\
struct Name {\
Tag Kind;\
Union Data;\
}

// CHECK-MESSAGES: :[[@LINE+1]]:44: warning: tagged union has more data members (4) than tags (3)
DECLARE_TAGGED_UNION_STRUCT(Tags3, Union4, TaggedUnionStructFromMacro);
Loading