[clang] 818338a - [clang] Add support for the "abstract" contextual keyword of MSVC
Hans Wennborg via cfe-commits
cfe-commits at lists.llvm.org
Mon May 31 01:45:11 PDT 2021
Author: Abbas Sabra
Date: 2021-05-31T10:44:53+02:00
New Revision: 818338add77411f5e9713247ea66142f332ef350
URL: https://github.com/llvm/llvm-project/commit/818338add77411f5e9713247ea66142f332ef350
DIFF: https://github.com/llvm/llvm-project/commit/818338add77411f5e9713247ea66142f332ef350.diff
LOG: [clang] Add support for the "abstract" contextual keyword of MSVC
https://docs.microsoft.com/en-us/cpp/extensions/abstract-cpp-component-extensions?view=msvc-160
Note: like the already supported "sealed" keyword, the "abstract"
keyword is supported by MSVC by default.
Differential revision: https://reviews.llvm.org/D102517
Added:
Modified:
clang/include/clang/AST/DeclCXX.h
clang/include/clang/Basic/DiagnosticGroups.td
clang/include/clang/Basic/DiagnosticParseKinds.td
clang/include/clang/Parse/Parser.h
clang/include/clang/Sema/DeclSpec.h
clang/include/clang/Sema/Sema.h
clang/lib/Parse/ParseDeclCXX.cpp
clang/lib/Sema/DeclSpec.cpp
clang/lib/Sema/SemaDecl.cpp
clang/test/SemaCXX/MicrosoftExtensions.cpp
Removed:
################################################################################
diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h
index 07c4eb261aac1..c9efc4b454968 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -1786,6 +1786,7 @@ class CXXRecordDecl : public RecordDecl {
static bool classofKind(Kind K) {
return K >= firstCXXRecord && K <= lastCXXRecord;
}
+ void markAbstract() { data().Abstract = true; }
};
/// Store information needed for an explicit specifier.
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index f6a4fbb1e04c8..8544607673c3e 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -1086,6 +1086,7 @@ def MicrosoftInclude : DiagGroup<"microsoft-include">;
def MicrosoftCppMacro : DiagGroup<"microsoft-cpp-macro">;
def MicrosoftFixedEnum : DiagGroup<"microsoft-fixed-enum">;
def MicrosoftSealed : DiagGroup<"microsoft-sealed">;
+def MicrosoftAbstract : DiagGroup<"microsoft-abstract">;
def MicrosoftUnqualifiedFriend : DiagGroup<"microsoft-unqualified-friend">;
def MicrosoftExceptionSpec : DiagGroup<"microsoft-exception-spec">;
def MicrosoftUsingDecl : DiagGroup<"microsoft-using-decl">;
@@ -1123,7 +1124,7 @@ def : DiagGroup<"msvc-include", [MicrosoftInclude]>;
// Warnings group for warnings about Microsoft extensions.
def Microsoft : DiagGroup<"microsoft",
[MicrosoftCharize, MicrosoftDrectveSection, MicrosoftInclude,
- MicrosoftCppMacro, MicrosoftFixedEnum, MicrosoftSealed,
+ MicrosoftCppMacro, MicrosoftFixedEnum, MicrosoftSealed, MicrosoftAbstract,
MicrosoftUnqualifiedFriend, MicrosoftExceptionSpec, MicrosoftUsingDecl,
MicrosoftMutableReference, MicrosoftPureDefinition,
MicrosoftUnionMemberReference, MicrosoftExplicitConstructorCall,
diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td
index 97cb7020f3452..b13c382c16ebc 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -922,10 +922,16 @@ def err_override_control_interface : Error<
def ext_ms_sealed_keyword : ExtWarn<
"'sealed' keyword is a Microsoft extension">,
InGroup<MicrosoftSealed>;
+def ext_ms_abstract_keyword : ExtWarn<
+ "'abstract' keyword is a Microsoft extension">,
+ InGroup<MicrosoftAbstract>;
def err_access_specifier_interface : Error<
"interface types cannot specify '%select{private|protected}0' access">;
+def err_duplicate_class_virt_specifier : Error<
+ "class already marked '%0'">;
+
def err_duplicate_virt_specifier : Error<
"class member already marked '%0'">;
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index e3e6509b025e5..c32ae6d2cf406 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -114,6 +114,7 @@ class Parser : public CodeCompletionHandler {
/// Contextual keywords for Microsoft extensions.
IdentifierInfo *Ident__except;
mutable IdentifierInfo *Ident_sealed;
+ mutable IdentifierInfo *Ident_abstract;
/// Ident_super - IdentifierInfo for "super", to support fast
/// comparison.
@@ -2913,6 +2914,7 @@ class Parser : public CodeCompletionHandler {
SourceLocation FriendLoc);
bool isCXX11FinalKeyword() const;
+ bool isClassCompatibleKeyword() const;
/// DeclaratorScopeObj - RAII object used in Parser::ParseDirectDeclarator to
/// enter a new C++ declarator scope and exit it when the function is
diff --git a/clang/include/clang/Sema/DeclSpec.h b/clang/include/clang/Sema/DeclSpec.h
index 3b16295941e48..423f4f4ee7b7c 100644
--- a/clang/include/clang/Sema/DeclSpec.h
+++ b/clang/include/clang/Sema/DeclSpec.h
@@ -2620,7 +2620,8 @@ class VirtSpecifiers {
VS_Final = 2,
VS_Sealed = 4,
// Represents the __final keyword, which is legal for gcc in pre-C++11 mode.
- VS_GNU_Final = 8
+ VS_GNU_Final = 8,
+ VS_Abstract = 16
};
VirtSpecifiers() : Specifiers(0), LastSpecifier(VS_None) { }
@@ -2636,6 +2637,7 @@ class VirtSpecifiers {
bool isFinalSpecified() const { return Specifiers & (VS_Final | VS_Sealed | VS_GNU_Final); }
bool isFinalSpelledSealed() const { return Specifiers & VS_Sealed; }
SourceLocation getFinalLoc() const { return VS_finalLoc; }
+ SourceLocation getAbstractLoc() const { return VS_abstractLoc; }
void clear() { Specifiers = 0; }
@@ -2649,7 +2651,7 @@ class VirtSpecifiers {
unsigned Specifiers;
Specifier LastSpecifier;
- SourceLocation VS_overrideLoc, VS_finalLoc;
+ SourceLocation VS_overrideLoc, VS_finalLoc, VS_abstractLoc;
SourceLocation FirstLocation;
SourceLocation LastLocation;
};
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 07518b37eb98a..65469418936cb 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -3147,6 +3147,7 @@ class Sema final {
void ActOnStartCXXMemberDeclarations(Scope *S, Decl *TagDecl,
SourceLocation FinalLoc,
bool IsFinalSpelledSealed,
+ bool IsAbstract,
SourceLocation LBraceLoc);
/// ActOnTagFinishDefinition - Invoked once we have finished parsing
diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp
index af3d0df53e91b..5f508dc30240b 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -1710,7 +1710,7 @@ void Parser::ParseClassSpecifier(tok::TokenKind TagTokKind,
TUK = Sema::TUK_Reference;
else if (Tok.is(tok::l_brace) ||
(getLangOpts().CPlusPlus && Tok.is(tok::colon)) ||
- (isCXX11FinalKeyword() &&
+ (isClassCompatibleKeyword() &&
(NextToken().is(tok::l_brace) || NextToken().is(tok::colon)))) {
if (DS.isFriendSpecified()) {
// C++ [class.friend]p2:
@@ -1726,14 +1726,18 @@ void Parser::ParseClassSpecifier(tok::TokenKind TagTokKind,
// Okay, this is a class definition.
TUK = Sema::TUK_Definition;
}
- } else if (isCXX11FinalKeyword() && (NextToken().is(tok::l_square) ||
- NextToken().is(tok::kw_alignas))) {
+ } else if (isClassCompatibleKeyword() &&
+ (NextToken().is(tok::l_square) ||
+ NextToken().is(tok::kw_alignas) ||
+ isCXX11VirtSpecifier(NextToken()) != VirtSpecifiers::VS_None)) {
// We can't tell if this is a definition or reference
// until we skipped the 'final' and C++11 attribute specifiers.
TentativeParsingAction PA(*this);
- // Skip the 'final' keyword.
- ConsumeToken();
+ // Skip the 'final', abstract'... keywords.
+ while (isClassCompatibleKeyword()) {
+ ConsumeToken();
+ }
// Skip C++11 attribute specifiers.
while (true) {
@@ -1982,7 +1986,7 @@ void Parser::ParseClassSpecifier(tok::TokenKind TagTokKind,
if (TUK == Sema::TUK_Definition) {
assert(Tok.is(tok::l_brace) ||
(getLangOpts().CPlusPlus && Tok.is(tok::colon)) ||
- isCXX11FinalKeyword());
+ isClassCompatibleKeyword());
if (SkipBody.ShouldSkip)
SkipCXXMemberSpecification(StartLoc, AttrFixitLoc, TagType,
TagOrTempResult.get());
@@ -2249,8 +2253,10 @@ VirtSpecifiers::Specifier Parser::isCXX11VirtSpecifier(const Token &Tok) const {
Ident_final = &PP.getIdentifierTable().get("final");
if (getLangOpts().GNUKeywords)
Ident_GNU_final = &PP.getIdentifierTable().get("__final");
- if (getLangOpts().MicrosoftExt)
+ if (getLangOpts().MicrosoftExt) {
Ident_sealed = &PP.getIdentifierTable().get("sealed");
+ Ident_abstract = &PP.getIdentifierTable().get("abstract");
+ }
Ident_override = &PP.getIdentifierTable().get("override");
}
@@ -2260,6 +2266,9 @@ VirtSpecifiers::Specifier Parser::isCXX11VirtSpecifier(const Token &Tok) const {
if (II == Ident_sealed)
return VirtSpecifiers::VS_Sealed;
+ if (II == Ident_abstract)
+ return VirtSpecifiers::VS_Abstract;
+
if (II == Ident_final)
return VirtSpecifiers::VS_Final;
@@ -2305,6 +2314,8 @@ void Parser::ParseOptionalCXX11VirtSpecifierSeq(VirtSpecifiers &VS,
<< VirtSpecifiers::getSpecifierName(Specifier);
} else if (Specifier == VirtSpecifiers::VS_Sealed) {
Diag(Tok.getLocation(), diag::ext_ms_sealed_keyword);
+ } else if (Specifier == VirtSpecifiers::VS_Abstract) {
+ Diag(Tok.getLocation(), diag::ext_ms_abstract_keyword);
} else if (Specifier == VirtSpecifiers::VS_GNU_Final) {
Diag(Tok.getLocation(), diag::ext_warn_gnu_final);
} else {
@@ -2327,6 +2338,16 @@ bool Parser::isCXX11FinalKeyword() const {
Specifier == VirtSpecifiers::VS_Sealed;
}
+/// isClassCompatibleKeyword - Determine whether the next token is a C++11
+/// 'final' or Microsoft 'sealed' or 'abstract' contextual keywords.
+bool Parser::isClassCompatibleKeyword() const {
+ VirtSpecifiers::Specifier Specifier = isCXX11VirtSpecifier();
+ return Specifier == VirtSpecifiers::VS_Final ||
+ Specifier == VirtSpecifiers::VS_GNU_Final ||
+ Specifier == VirtSpecifiers::VS_Sealed ||
+ Specifier == VirtSpecifiers::VS_Abstract;
+}
+
/// Parse a C++ member-declarator up to, but not including, the optional
/// brace-or-equal-initializer or pure-specifier.
bool Parser::ParseCXXMemberDeclaratorBeforeInitializer(
@@ -2893,8 +2914,13 @@ Parser::ParseCXXClassMemberDeclaration(AccessSpecifier AS,
HasStaticInitializer = true;
}
+ if (PureSpecLoc.isValid() && VS.getAbstractLoc().isValid()) {
+ Diag(PureSpecLoc, diag::err_duplicate_virt_specifier) << "abstract";
+ }
if (ThisDecl && PureSpecLoc.isValid())
Actions.ActOnPureSpecifier(ThisDecl, PureSpecLoc);
+ else if (ThisDecl && VS.getAbstractLoc().isValid())
+ Actions.ActOnPureSpecifier(ThisDecl, VS.getAbstractLoc());
// Handle the initializer.
if (HasInClassInit != ICIS_NoInit) {
@@ -3279,30 +3305,53 @@ void Parser::ParseCXXMemberSpecification(SourceLocation RecordLoc,
Actions.ActOnTagStartDefinition(getCurScope(), TagDecl);
SourceLocation FinalLoc;
+ SourceLocation AbstractLoc;
bool IsFinalSpelledSealed = false;
+ bool IsAbstract = false;
// Parse the optional 'final' keyword.
if (getLangOpts().CPlusPlus && Tok.is(tok::identifier)) {
- VirtSpecifiers::Specifier Specifier = isCXX11VirtSpecifier(Tok);
- assert((Specifier == VirtSpecifiers::VS_Final ||
- Specifier == VirtSpecifiers::VS_GNU_Final ||
- Specifier == VirtSpecifiers::VS_Sealed) &&
+ while (true) {
+ VirtSpecifiers::Specifier Specifier = isCXX11VirtSpecifier(Tok);
+ if (Specifier == VirtSpecifiers::VS_None)
+ break;
+ if (isCXX11FinalKeyword()) {
+ if (FinalLoc.isValid()) {
+ auto Skipped = ConsumeToken();
+ Diag(Skipped, diag::err_duplicate_class_virt_specifier)
+ << VirtSpecifiers::getSpecifierName(Specifier);
+ } else {
+ FinalLoc = ConsumeToken();
+ if (Specifier == VirtSpecifiers::VS_Sealed)
+ IsFinalSpelledSealed = true;
+ }
+ } else {
+ if (AbstractLoc.isValid()) {
+ auto Skipped = ConsumeToken();
+ Diag(Skipped, diag::err_duplicate_class_virt_specifier)
+ << VirtSpecifiers::getSpecifierName(Specifier);
+ } else {
+ AbstractLoc = ConsumeToken();
+ IsAbstract = true;
+ }
+ }
+ if (TagType == DeclSpec::TST_interface)
+ Diag(FinalLoc, diag::err_override_control_interface)
+ << VirtSpecifiers::getSpecifierName(Specifier);
+ else if (Specifier == VirtSpecifiers::VS_Final)
+ Diag(FinalLoc, getLangOpts().CPlusPlus11
+ ? diag::warn_cxx98_compat_override_control_keyword
+ : diag::ext_override_control_keyword)
+ << VirtSpecifiers::getSpecifierName(Specifier);
+ else if (Specifier == VirtSpecifiers::VS_Sealed)
+ Diag(FinalLoc, diag::ext_ms_sealed_keyword);
+ else if (Specifier == VirtSpecifiers::VS_Abstract)
+ Diag(AbstractLoc, diag::ext_ms_abstract_keyword);
+ else if (Specifier == VirtSpecifiers::VS_GNU_Final)
+ Diag(FinalLoc, diag::ext_warn_gnu_final);
+ }
+ assert((FinalLoc.isValid() || AbstractLoc.isValid()) &&
"not a class definition");
- FinalLoc = ConsumeToken();
- IsFinalSpelledSealed = Specifier == VirtSpecifiers::VS_Sealed;
-
- if (TagType == DeclSpec::TST_interface)
- Diag(FinalLoc, diag::err_override_control_interface)
- << VirtSpecifiers::getSpecifierName(Specifier);
- else if (Specifier == VirtSpecifiers::VS_Final)
- Diag(FinalLoc, getLangOpts().CPlusPlus11
- ? diag::warn_cxx98_compat_override_control_keyword
- : diag::ext_override_control_keyword)
- << VirtSpecifiers::getSpecifierName(Specifier);
- else if (Specifier == VirtSpecifiers::VS_Sealed)
- Diag(FinalLoc, diag::ext_ms_sealed_keyword);
- else if (Specifier == VirtSpecifiers::VS_GNU_Final)
- Diag(FinalLoc, diag::ext_warn_gnu_final);
// Parse any C++11 attributes after 'final' keyword.
// These attributes are not allowed to appear here,
@@ -3375,7 +3424,7 @@ void Parser::ParseCXXMemberSpecification(SourceLocation RecordLoc,
if (TagDecl)
Actions.ActOnStartCXXMemberDeclarations(getCurScope(), TagDecl, FinalLoc,
- IsFinalSpelledSealed,
+ IsFinalSpelledSealed, IsAbstract,
T.getOpenLocation());
// C++ 11p3: Members of a class defined with the keyword class are private
diff --git a/clang/lib/Sema/DeclSpec.cpp b/clang/lib/Sema/DeclSpec.cpp
index a2b55c0fec294..72d9ea6dd3bf3 100644
--- a/clang/lib/Sema/DeclSpec.cpp
+++ b/clang/lib/Sema/DeclSpec.cpp
@@ -1475,6 +1475,7 @@ bool VirtSpecifiers::SetSpecifier(Specifier VS, SourceLocation Loc,
case VS_GNU_Final:
case VS_Sealed:
case VS_Final: VS_finalLoc = Loc; break;
+ case VS_Abstract: VS_abstractLoc = Loc; break;
}
return false;
@@ -1487,5 +1488,6 @@ const char *VirtSpecifiers::getSpecifierName(Specifier VS) {
case VS_Final: return "final";
case VS_GNU_Final: return "__final";
case VS_Sealed: return "sealed";
+ case VS_Abstract: return "abstract";
}
}
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index e08e8d8346c03..5e0faacf0fdb0 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -16456,6 +16456,7 @@ Decl *Sema::ActOnObjCContainerStartDefinition(Decl *IDecl) {
void Sema::ActOnStartCXXMemberDeclarations(Scope *S, Decl *TagD,
SourceLocation FinalLoc,
bool IsFinalSpelledSealed,
+ bool IsAbstract,
SourceLocation LBraceLoc) {
AdjustDeclIfTemplate(TagD);
CXXRecordDecl *Record = cast<CXXRecordDecl>(TagD);
@@ -16465,11 +16466,14 @@ void Sema::ActOnStartCXXMemberDeclarations(Scope *S, Decl *TagD,
if (!Record->getIdentifier())
return;
- if (FinalLoc.isValid())
+ if (IsAbstract)
+ Record->markAbstract();
+
+ if (FinalLoc.isValid()) {
Record->addAttr(FinalAttr::Create(
Context, FinalLoc, AttributeCommonInfo::AS_Keyword,
static_cast<FinalAttr::Spelling>(IsFinalSpelledSealed)));
-
+ }
// C++ [class]p2:
// [...] The class-name is also inserted into the scope of the
// class itself; this is known as the injected-class-name. For
diff --git a/clang/test/SemaCXX/MicrosoftExtensions.cpp b/clang/test/SemaCXX/MicrosoftExtensions.cpp
index b1f8cd96d5052..5dfadae3682a5 100644
--- a/clang/test/SemaCXX/MicrosoftExtensions.cpp
+++ b/clang/test/SemaCXX/MicrosoftExtensions.cpp
@@ -462,6 +462,77 @@ class SealedDestructor { // expected-note {{mark 'SealedDestructor' as 'sealed'
virtual ~SealedDestructor() sealed; // expected-warning {{class with destructor marked 'sealed' cannot be inherited from}}
};
+// expected-warning at +1 {{'abstract' keyword is a Microsoft extension}}
+class AbstractClass abstract {
+ int i;
+};
+
+// expected-error at +1 {{variable type 'AbstractClass' is an abstract class}}
+AbstractClass abstractInstance;
+
+// expected-warning at +4 {{abstract class is marked 'sealed'}}
+// expected-note at +3 {{'AbstractAndSealedClass' declared here}}
+// expected-warning at +2 {{'abstract' keyword is a Microsoft extension}}
+// expected-warning at +1 {{'sealed' keyword is a Microsoft extension}}
+class AbstractAndSealedClass abstract sealed {}; // Does no really make sense, but allowed
+
+// expected-error at +1 {{variable type 'AbstractAndSealedClass' is an abstract class}}
+AbstractAndSealedClass abstractAndSealedInstance;
+// expected-error at +1 {{base 'AbstractAndSealedClass' is marked 'sealed'}}
+class InheritFromAbstractAndSealed : AbstractAndSealedClass {};
+
+#if __cplusplus <= 199711L
+// expected-warning at +4 {{'final' keyword is a C++11 extension}}
+// expected-warning at +3 {{'final' keyword is a C++11 extension}}
+#endif
+// expected-error at +1 {{class already marked 'final'}}
+class TooManyVirtSpecifiers1 final final {};
+#if __cplusplus <= 199711L
+// expected-warning at +4 {{'final' keyword is a C++11 extension}}
+#endif
+// expected-warning at +2 {{'sealed' keyword is a Microsoft extension}}
+// expected-error at +1 {{class already marked 'sealed'}}
+class TooManyVirtSpecifiers2 final sealed {};
+#if __cplusplus <= 199711L
+// expected-warning at +6 {{'final' keyword is a C++11 extension}}
+// expected-warning at +5 {{'final' keyword is a C++11 extension}}
+#endif
+// expected-warning at +3 {{abstract class is marked 'final'}}
+// expected-warning at +2 {{'abstract' keyword is a Microsoft extension}}
+// expected-error at +1 {{class already marked 'final'}}
+class TooManyVirtSpecifiers3 final abstract final {};
+#if __cplusplus <= 199711L
+// expected-warning at +6 {{'final' keyword is a C++11 extension}}
+#endif
+// expected-warning at +4 {{abstract class is marked 'final'}}
+// expected-warning at +3 {{'abstract' keyword is a Microsoft extension}}
+// expected-warning at +2 {{'abstract' keyword is a Microsoft extension}}
+// expected-error at +1 {{class already marked 'abstract'}}
+class TooManyVirtSpecifiers4 abstract final abstract {};
+
+class Base {
+ virtual void i();
+};
+class AbstractFunctionInClass : public Base {
+ // expected-note at +2 {{unimplemented pure virtual method 'f' in 'AbstractFunctionInClass'}}
+ // expected-warning at +1 {{'abstract' keyword is a Microsoft extension}}
+ virtual void f() abstract;
+ // expected-warning at +1 {{'abstract' keyword is a Microsoft extension}}
+ void g() abstract; // expected-error {{'g' is not virtual and cannot be declared pure}}
+ // expected-note at +2 {{unimplemented pure virtual method 'h' in 'AbstractFunctionInClass'}}
+ // expected-warning at +1 {{'abstract' keyword is a Microsoft extension}}
+ virtual void h() abstract = 0; // expected-error {{class member already marked 'abstract'}}
+#if __cplusplus <= 199711L
+ // expected-warning at +4 {{'override' keyword is a C++11 extension}}
+#endif
+ // expected-note at +2 {{unimplemented pure virtual method 'i' in 'AbstractFunctionInClass'}}
+ // expected-warning at +1 {{'abstract' keyword is a Microsoft extension}}
+ virtual void i() abstract override;
+};
+
+// expected-error at +1 {{variable type 'AbstractFunctionInClass' is an abstract class}}
+AbstractFunctionInClass abstractFunctionInClassInstance;
+
void AfterClassBody() {
// expected-warning at +1 {{attribute 'deprecated' is ignored, place it after "struct" to apply attribute to type declaration}}
struct D {} __declspec(deprecated);
More information about the cfe-commits
mailing list