diff --git a/clang/include/clang/AST/CommentSema.h b/clang/include/clang/AST/CommentSema.h --- a/clang/include/clang/AST/CommentSema.h +++ b/clang/include/clang/AST/CommentSema.h @@ -217,6 +217,9 @@ bool isTemplateOrSpecialization(); bool isRecordLikeDecl(); bool isClassOrStructDecl(); + /// \return \c true if the declaration that this comment is attached to + /// declares either struct, class or tag typedef. + bool isClassOrStructOrTagTypedefDecl(); bool isUnionDecl(); bool isObjCInterfaceDecl(); bool isObjCProtocolDecl(); diff --git a/clang/lib/AST/CommentSema.cpp b/clang/lib/AST/CommentSema.cpp --- a/clang/lib/AST/CommentSema.cpp +++ b/clang/lib/AST/CommentSema.cpp @@ -12,6 +12,7 @@ #include "clang/AST/CommentDiagnostic.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclTemplate.h" +#include "clang/Basic/LLVM.h" #include "clang/Basic/SourceManager.h" #include "clang/Lex/Preprocessor.h" #include "llvm/ADT/SmallString.h" @@ -134,7 +135,9 @@ unsigned DiagSelect; switch (Comment->getCommandID()) { case CommandTraits::KCI_class: - DiagSelect = (!isClassOrStructDecl() && !isClassTemplateDecl()) ? 1 : 0; + DiagSelect = + (!isClassOrStructOrTagTypedefDecl() && !isClassTemplateDecl()) ? 1 + : 0; // Allow @class command on @interface declarations. // FIXME. Currently, \class and @class are indistinguishable. So, // \class is also allowed on an @interface declaration @@ -148,7 +151,7 @@ DiagSelect = !isObjCProtocolDecl() ? 3 : 0; break; case CommandTraits::KCI_struct: - DiagSelect = !isClassOrStructDecl() ? 4 : 0; + DiagSelect = !isClassOrStructOrTagTypedefDecl() ? 4 : 0; break; case CommandTraits::KCI_union: DiagSelect = !isUnionDecl() ? 5 : 0; @@ -935,15 +938,50 @@ return RD->isUnion(); return false; } +static bool isClassOrStructDeclImpl(const Decl *D) { + if (auto *record = dyn_cast_or_null(D)) + return !record->isUnion(); + + return false; +} bool Sema::isClassOrStructDecl() { if (!ThisDeclInfo) return false; if (!ThisDeclInfo->IsFilled) inspectThisDecl(); - return ThisDeclInfo->CurrentDecl && - isa(ThisDeclInfo->CurrentDecl) && - !isUnionDecl(); + + if (!ThisDeclInfo->CurrentDecl) + return false; + + return isClassOrStructDeclImpl(ThisDeclInfo->CurrentDecl); +} + +bool Sema::isClassOrStructOrTagTypedefDecl() { + if (!ThisDeclInfo) + return false; + if (!ThisDeclInfo->IsFilled) + inspectThisDecl(); + + if (!ThisDeclInfo->CurrentDecl) + return false; + + if (isClassOrStructDeclImpl(ThisDeclInfo->CurrentDecl)) + return true; + + if (auto *ThisTypedefDecl = dyn_cast(ThisDeclInfo->CurrentDecl)) { + auto UnderlyingType = ThisTypedefDecl->getUnderlyingType(); + if (auto ThisElaboratedType = dyn_cast(UnderlyingType)) { + auto DesugaredType = ThisElaboratedType->desugar(); + if (auto *DesugaredTypePtr = DesugaredType.getTypePtrOrNull()) { + if (auto *ThisRecordType = dyn_cast(DesugaredTypePtr)) { + return isClassOrStructDeclImpl(ThisRecordType->getAsRecordDecl()); + } + } + } + } + + return false; } bool Sema::isClassTemplateDecl() { diff --git a/clang/test/Sema/warn-documentation-tag-typedef.cpp b/clang/test/Sema/warn-documentation-tag-typedef.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Sema/warn-documentation-tag-typedef.cpp @@ -0,0 +1,13 @@ +// RUN: %clang_cc1 -Wdocumentation -fsyntax-only %s 2>&1 | FileCheck -allow-empty %s + +/*! +@class Foo +*/ +typedef class { } Foo; +// CHECK-NOT: warning: + +/*! +@struct Bar +*/ +typedef struct { } Bar; +// CHECK-NOT: warning: