Index: clang/include/clang/AST/CommentSema.h =================================================================== --- clang/include/clang/AST/CommentSema.h +++ 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(); Index: clang/lib/AST/CommentSema.cpp =================================================================== --- clang/lib/AST/CommentSema.cpp +++ clang/lib/AST/CommentSema.cpp @@ -134,7 +134,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 +150,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 +937,53 @@ return RD->isUnion(); return false; } +static bool isClassOrStructDeclImpl(const Decl *D) { + if (!D) + return false; + + if (auto *record = dyn_cast(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 *typedefDecl = dyn_cast(ThisDeclInfo->CurrentDecl)) { + auto underlyingType = typedefDecl->getUnderlyingType(); + if (auto elaboratedType = dyn_cast(underlyingType)) { + auto desugaredType = elaboratedType->desugar(); + if (auto *type = desugaredType.getTypePtrOrNull()) { + if (auto *recordType = dyn_cast(type)) { + return isClassOrStructDeclImpl(recordType->getAsRecordDecl()); + } + } + } + } + + return false; } bool Sema::isClassTemplateDecl() { Index: clang/test/Sema/warn-documentation-tag-typedef.cpp =================================================================== --- /dev/null +++ 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: