diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -536,6 +536,10 @@ "dependent using declaration resolved to type without 'typename'">; def err_using_decl_nested_name_specifier_is_not_class : Error< "using declaration in class refers into '%0', which is not a class">; +def warn_cxx17_compat_using_decl_non_member_enumerator : Warning< + "member using declaration naming non-class '%0' enumerator is " + "incompatible with C++ standards before C++20">, InGroup, + DefaultIgnore; def err_using_decl_nested_name_specifier_is_current_class : Error< "using declaration refers to its own class">; def err_using_decl_nested_name_specifier_is_not_base_class : Error< @@ -544,6 +548,16 @@ "%0 is not a direct base of %1, cannot inherit constructors">; def err_using_decl_can_not_refer_to_class_member : Error< "using declaration cannot refer to class member">; +def warn_cxx17_compat_using_decl_class_member_enumerator : Warning< + "member using declaration naming a non-member enumerator is incompatible " + "with C++ standards before C++20">, InGroup, DefaultIgnore; +def ext_using_decl_class_member_enumerator : ExtWarn< + "member using declaration naming a non-member enumerator is " + "a C++20 extension">, InGroup; +def err_using_enum_lacks_definition : Error< + "enum named by using-enum declaration lacks a definition">; +def err_using_enum_is_dependent : Error< + "using-enum cannot name a dependent type">; def err_ambiguous_inherited_constructor : Error< "constructor of %0 inherited from multiple base class subobjects">; def note_ambiguous_inherited_constructor_using : Note< @@ -553,8 +567,12 @@ "a const variable|a constexpr variable}0 instead">; def err_using_decl_can_not_refer_to_namespace : Error< "using declaration cannot refer to a namespace">; -def err_using_decl_can_not_refer_to_scoped_enum : Error< - "using declaration cannot refer to a scoped enumerator">; +def warn_cxx17_compat_using_decl_scoped_enumerator: Warning< + "using declaration naming a scoped enumerator is incompatible with " + "C++ standards before C++20">, InGroup, DefaultIgnore; +def ext_using_decl_scoped_enumerator : ExtWarn< + "using declaration naming a scoped enumerator is a C++20 extension">, + InGroup; def err_using_decl_constructor : Error< "using declaration cannot refer to a constructor">; def warn_cxx98_compat_using_decl_constructor : Warning< diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -5714,11 +5714,12 @@ const CXXScopeSpec &SS, SourceLocation NameLoc, const LookupResult &Previous); - bool CheckUsingDeclQualifier(SourceLocation UsingLoc, - bool HasTypename, + bool CheckUsingDeclQualifier(SourceLocation UsingLoc, bool HasTypename, const CXXScopeSpec &SS, const DeclarationNameInfo &NameInfo, - SourceLocation NameLoc); + SourceLocation NameLoc, + const LookupResult *R = nullptr, + const UsingDecl *UD = nullptr); NamedDecl *BuildUsingDeclaration(Scope *S, AccessSpecifier AS, SourceLocation UsingLoc, diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -11659,7 +11659,7 @@ // This is invalid (during instantiation) in C++03 because B::foo // resolves to the using decl in B, which is not a base class of D. // We can't diagnose it immediately because C is an unknown - // specialization. The UsingShadowDecl in D then points directly + // specialization. The UsingShadowDecl in D then points directly // to A::foo, which will look well-formed when we instantiate. // The right solution is to not collapse the shadow-decl chain. if (!getLangOpts().CPlusPlus11 && CurContext->isRecord()) @@ -12088,11 +12088,6 @@ SS, IdentLoc, Previous)) return nullptr; - // Check for bad qualifiers. - if (CheckUsingDeclQualifier(UsingLoc, HasTypenameKeyword, SS, NameInfo, - IdentLoc)) - return nullptr; - // 'using_if_exists' doesn't make sense on an inherited constructor. if (IsUsingIfExists && UsingName.getName().getNameKind() == DeclarationName::CXXConstructorName) { @@ -12104,6 +12099,11 @@ NestedNameSpecifierLoc QualifierLoc = SS.getWithLocInContext(Context); if (!LookupContext || EllipsisLoc.isValid()) { NamedDecl *D; + // Dependent scope, or an unexpanded pack + if (!LookupContext && CheckUsingDeclQualifier(UsingLoc, HasTypenameKeyword, + SS, NameInfo, IdentLoc)) + return nullptr; + if (HasTypenameKeyword) { // FIXME: not all declaration name kinds are legal here D = UnresolvedUsingTypenameDecl::Create(Context, CurContext, @@ -12156,6 +12156,11 @@ LookupQualifiedName(R, LookupContext); + // Validate the context, now we have a lookup + if (CheckUsingDeclQualifier(UsingLoc, HasTypenameKeyword, SS, NameInfo, + IdentLoc, &R)) + return nullptr; + if (R.empty() && IsUsingIfExists) R.addDecl(UnresolvedUsingIfExistsDecl::Create(Context, CurContext, UsingLoc, UsingName.getName()), @@ -12261,16 +12266,6 @@ return BuildInvalid(); } - // C++14 [namespace.udecl]p7: - // A using-declaration shall not name a scoped enumerator. - if (auto *ED = R.getAsSingle()) { - if (cast(ED->getDeclContext())->isScoped()) { - Diag(IdentLoc, diag::err_using_decl_can_not_refer_to_scoped_enum) - << SS.getRange(); - return BuildInvalid(); - } - } - UsingDecl *UD = BuildValid(); // Some additional rules apply to inheriting constructors. @@ -12410,48 +12405,83 @@ return false; } - /// Checks that the given nested-name qualifier used in a using decl /// in the current context is appropriately related to the current /// scope. If an error is found, diagnoses it and returns true. -bool Sema::CheckUsingDeclQualifier(SourceLocation UsingLoc, - bool HasTypename, +/// R is nullptr, if the caller has not (yet) done a lookup, otherwise it's the +/// result of that lookup. UD is likewise nullptr, except when we have an +/// already-populated UsingDecl whose shadow decls contain the same information +/// (i.e. we're instantiating a UsingDecl with non-dependent scope). +bool Sema::CheckUsingDeclQualifier(SourceLocation UsingLoc, bool HasTypename, const CXXScopeSpec &SS, const DeclarationNameInfo &NameInfo, - SourceLocation NameLoc) { + SourceLocation NameLoc, + const LookupResult *R, const UsingDecl *UD) { DeclContext *NamedContext = computeDeclContext(SS); + assert(bool(NamedContext) == (R || UD) && !(R && UD) && + "resolvable context must have exactly one set of decls"); + + // C++ 20 permits using an enumerator that does not have a class-hierarchy + // relationship. + bool Cxx20Enumerator = false; + if (NamedContext) { + EnumConstantDecl *EC = nullptr; + if (R) + EC = R->getAsSingle(); + else if (UD && UD->shadow_size() == 1) + EC = dyn_cast(UD->shadow_begin()->getTargetDecl()); + if (EC) + Cxx20Enumerator = getLangOpts().CPlusPlus20; + + if (auto *ED = dyn_cast(NamedContext)) { + // C++14 [namespace.udecl]p7: + // A using-declaration shall not name a scoped enumerator. + // C++20 p1099 permits enumerators. + if (EC && R && ED->isScoped()) + Diag(SS.getBeginLoc(), + getLangOpts().CPlusPlus20 + ? diag::warn_cxx17_compat_using_decl_scoped_enumerator + : diag::ext_using_decl_scoped_enumerator) + << SS.getRange(); + + // We want to consider the scope of the enumerator + NamedContext = ED->getDeclContext(); + } + } if (!CurContext->isRecord()) { // C++03 [namespace.udecl]p3: // C++0x [namespace.udecl]p8: // A using-declaration for a class member shall be a member-declaration. + // C++20 [namespace.udecl]p7 + // ... other than an enumerator ... // If we weren't able to compute a valid scope, it might validly be a - // dependent class scope or a dependent enumeration unscoped scope. If - // we have a 'typename' keyword, the scope must resolve to a class type. - if ((HasTypename && !NamedContext) || - (NamedContext && NamedContext->getRedeclContext()->isRecord())) { - auto *RD = NamedContext - ? cast(NamedContext->getRedeclContext()) - : nullptr; - if (RD && RequireCompleteDeclContext(const_cast(SS), RD)) - RD = nullptr; - - Diag(NameLoc, diag::err_using_decl_can_not_refer_to_class_member) + // dependent class or enumeration scope. If we have a 'typename' keyword, + // the scope must resolve to a class type. + if (NamedContext ? !NamedContext->getRedeclContext()->isRecord() + : !HasTypename) + return false; // OK + + Diag(NameLoc, + Cxx20Enumerator + ? diag::warn_cxx17_compat_using_decl_class_member_enumerator + : diag::err_using_decl_can_not_refer_to_class_member) << SS.getRange(); - // If we have a complete, non-dependent source type, try to suggest a - // way to get the same effect. - if (!RD) - return true; + if (Cxx20Enumerator) + return false; // OK - // Find what this using-declaration was referring to. - LookupResult R(*this, NameInfo, LookupOrdinaryName); - R.setHideTags(false); - R.suppressDiagnostics(); - LookupQualifiedName(R, RD); + auto *RD = NamedContext + ? cast(NamedContext->getRedeclContext()) + : nullptr; + if (RD && !RequireCompleteDeclContext(const_cast(SS), RD)) { + // See if there's a helpful fixit - if (R.getAsSingle()) { + if (!R) { + // We will have already diagnosed the problem on the template + // definition, Maybe we should do so again? + } else if (R->getAsSingle()) { if (getLangOpts().CPlusPlus11) { // Convert 'using X::Y;' to 'using Y = X::Y;'. Diag(SS.getBeginLoc(), diag::note_using_decl_class_member_workaround) @@ -12468,7 +12498,7 @@ << FixItHint::CreateInsertion( InsertLoc, " " + NameInfo.getName().getAsString()); } - } else if (R.getAsSingle()) { + } else if (R->getAsSingle()) { // Don't provide a fixit outside C++11 mode; we don't want to suggest // repeating the type of the static data member here. FixItHint FixIt; @@ -12481,7 +12511,7 @@ Diag(UsingLoc, diag::note_using_decl_class_member_workaround) << 2 // reference declaration << FixIt; - } else if (R.getAsSingle()) { + } else if (R->getAsSingle()) { // Don't provide a fixit outside C++11 mode; we don't want to suggest // repeating the type of the enumeration here, and we can't do so if // the type is anonymous. @@ -12497,15 +12527,11 @@ << (getLangOpts().CPlusPlus11 ? 4 : 3) // const[expr] variable << FixIt; } - return true; } - // Otherwise, this might be valid. - return false; + return true; // Fail } - // The current scope is a record. - // If the named context is dependent, we can't decide much. if (!NamedContext) { // FIXME: in C++0x, we can diagnose if we can prove that the @@ -12517,12 +12543,19 @@ return false; } + // The current scope is a record. if (!NamedContext->isRecord()) { // Ideally this would point at the last name in the specifier, // but we don't have that level of source info. - Diag(SS.getRange().getBegin(), - diag::err_using_decl_nested_name_specifier_is_not_class) - << SS.getScopeRep() << SS.getRange(); + Diag(SS.getBeginLoc(), + Cxx20Enumerator + ? diag::warn_cxx17_compat_using_decl_non_member_enumerator + : diag::err_using_decl_nested_name_specifier_is_not_class) + << SS.getScopeRep() << SS.getRange(); + + if (Cxx20Enumerator) + return false; // OK + return true; } @@ -12538,19 +12571,25 @@ if (cast(CurContext)->isProvablyNotDerivedFrom( cast(NamedContext))) { + + if (Cxx20Enumerator) { + Diag(NameLoc, diag::warn_cxx17_compat_using_decl_non_member_enumerator) + << SS.getRange(); + return false; + } + if (CurContext == NamedContext) { - Diag(NameLoc, + Diag(SS.getBeginLoc(), diag::err_using_decl_nested_name_specifier_is_current_class) - << SS.getRange(); - return true; + << SS.getRange(); + return !getLangOpts().CPlusPlus20; } if (!cast(NamedContext)->isInvalidDecl()) { - Diag(SS.getRange().getBegin(), + Diag(SS.getBeginLoc(), diag::err_using_decl_nested_name_specifier_is_not_base_class) - << SS.getScopeRep() - << cast(CurContext) - << SS.getRange(); + << SS.getScopeRep() << cast(CurContext) + << SS.getRange(); } return true; } diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp --- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -3067,10 +3067,9 @@ } if (!NewUD->isInvalidDecl() && - SemaRef.CheckUsingDeclQualifier(D->getUsingLoc(), D->hasTypename(), - SS, NameInfo, D->getLocation())) + SemaRef.CheckUsingDeclQualifier(D->getUsingLoc(), D->hasTypename(), SS, + NameInfo, D->getLocation(), nullptr, D)) NewUD->setInvalidDecl(); - SemaRef.Context.setInstantiatedFromUsingDecl(NewUD, D); NewUD->setAccess(D->getAccess()); Owner->addDecl(NewUD); @@ -3079,6 +3078,8 @@ if (NewUD->isInvalidDecl()) return NewUD; + // If the using scope was dependent, or we had dependent bases, we need to + // recheck the inheritance if (NameInfo.getName().getNameKind() == DeclarationName::CXXConstructorName) SemaRef.CheckInheritingConstructorUsingDecl(NewUD); diff --git a/clang/test/CXX/dcl.dcl/basic.namespace/namespace.udecl/p3.cpp b/clang/test/CXX/dcl.dcl/basic.namespace/namespace.udecl/p3.cpp --- a/clang/test/CXX/dcl.dcl/basic.namespace/namespace.udecl/p3.cpp +++ b/clang/test/CXX/dcl.dcl/basic.namespace/namespace.udecl/p3.cpp @@ -17,6 +17,7 @@ }; class C { +public: int g(); }; @@ -42,7 +43,7 @@ #endif using B::EC; - using B::EC::ec; // expected-error {{not a class}} expected-warning 0-1 {{C++11}} + using B::EC::ec; // expected-warning {{a C++20 extension}} expected-warning 0-1 {{C++11}} }; namespace test1 { diff --git a/clang/test/CXX/dcl.dcl/basic.namespace/namespace.udecl/p7-cxx20.cpp b/clang/test/CXX/dcl.dcl/basic.namespace/namespace.udecl/p7-cxx20.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CXX/dcl.dcl/basic.namespace/namespace.udecl/p7-cxx20.cpp @@ -0,0 +1,271 @@ +// RUN: %clang_cc1 -fsyntax-only -std=c++17 -verify %s +// RUN: %clang_cc1 -fsyntax-only -std=c++20 -verify %s + +// p1099 'using SCOPEDENUM::MEMBER;' + +namespace Zero { +namespace Bob { +enum class Kevin { + Stuart, + AlsoStuart +#if __cplusplus >= 202002L +// expected-note@-3{{target of using declaration}} +// expected-note@-3{{target of using declaration}} +#endif +}; +} // namespace Bob + +using Bob::Kevin::Stuart; +#if __cplusplus < 202002L +// expected-warning@-2{{using declaration naming a scoped enumerator is a C++20 extension}} +#else +using Bob::Kevin::Stuart; + +auto b = Stuart; + +namespace Foo { +int Stuart; // expected-note{{conflicting declaration}} +using Bob::Kevin::Stuart; // expected-error{{target of using declaration conflicts}} + +using Bob::Kevin::AlsoStuart; // expected-note{{using declaration}} +int AlsoStuart; // expected-error{{declaration conflicts with target}} +} // namespace Foo +#endif + +} // namespace Zero + +namespace One { + +// derived from [namespace.udecl]/3 +enum class button { up, + down }; +struct S { + using button::up; +#if __cplusplus < 202002L + // expected-warning@-2{{a C++20 extension}} + // expected-error@-3{{using declaration in class}} +#else + button b = up; +#endif +}; + +#if __cplusplus >= 202002L +// some more +struct T : S { + button c = up; +}; +#endif +enum E2 { e2 }; +} // namespace One + +namespace Two { +enum class E1 { e1 }; + +struct S { + using One::e2; +#if __cplusplus < 202002L + // expected-error@-2{{using declaration in class}} +#else + One::E2 c = e2; +#endif +}; + +} // namespace Two + +namespace Three { + +enum E3 { e3 }; +struct e3; + +struct S { + using Three::e3; // expected-error{{using declaration in class}} + + enum class E4 { e4 }; + enum E5 { e5 }; +}; + +using S::e5; +using S::E4::e4; +#if __cplusplus < 202002L +// expected-error@-3{{using declaration cannot refer to class member}} +// expected-note@-4{{use a constexpr variable instead}} +// expected-warning@-4{{a C++20 extension}} +// expected-error@-5{{using declaration cannot refer to class member}} +// expected-note@-6{{use a constexpr variable instead}} +#else +auto a = e4; +auto b = e5; +#endif +} // namespace Three + +namespace Four { + +template +struct TPL { + enum class E1 { e1 }; + struct IN { + enum class E2 { e2 }; + }; + +protected: + enum class E3 { e3 }; // expected-note{{declared protected here}} +}; + +using TPL::E1::e1; +#if __cplusplus < 202002L +// expected-warning@-2{{a C++20 extension}} +// expected-error@-3{{using declaration cannot refer to class member}} +// expected-note@-4{{use a constexpr variable instead}} +#else +using TPL::IN::E2::e2; + +auto a = e1; +auto b = e2; +#endif + +enum class E4 { e4 }; +template +struct DER : TPL { + using TPL::E1::e1; +#if __cplusplus < 202002L + // expected-warning@-2{{a C++20 extension}} + // expected-warning@-3{{using declaration naming a scoped}} + // expected-error@-4{{which is not a base}} +#endif + using TPL::E3::e3; // expected-error{{is a protected member}} +#if __cplusplus < 202002L + // expected-warning@-2 2{{using declaration naming a scoped}} + // expected-error@-3{{which is not a base}} +#endif + + using E4::e4; +#if __cplusplus < 202002L + // expected-warning@-2{{a C++20 extension}} + // expected-error@-3{{which is not a class}} +#else + auto Foo() { return e1; } + auto Bar() { return e2; } +#endif +}; + +DER x; // expected-note{{requested here}} +DER y; +#if __cplusplus < 202002L +// expected-note@-2{{requested here}} +#else +auto y1 = y.Foo(); +auto y2 = y.Bar(); +#endif +} // namespace Four + +namespace Five { +template +struct Quux { + enum class Q : unsigned; // expected-note{{member is declared here}} + enum class R : unsigned { i = I, + k = K }; +}; + +using Quux<1, 2>::Q::nothing; // expected-error{{implicit instantiation of undefined}} +using Quux<1, 2>::R::i; +#if __cplusplus < 202002L +// expected-warning@-2{{a C++20 extension}} +// expected-error@-3{{using declaration cannot refer to class member}} +// expected-note@-4{{use a constexpr variable instead}} +#endif + +} // namespace Five + +namespace Six { +template +struct Quux { + enum class Q : unsigned; // expected-note{{member is declared here}} + enum class R : unsigned { i = I, + k = K }; +}; + +template struct Fido { + using Quux::Q::nothing; // expected-error{{implicit instantiation of undefined}} +}; + +Fido<2> a; // expected-note{{in instantiation}} + +} // namespace Six + +namespace Seven { +template +struct Quux { + enum class R : unsigned { i = I, + k = K }; +}; + +template struct Toto { + using Quux::R::i; +#if __cplusplus < 202002L + // expected-warning@-2{{a C++20 extension}} +// expected-error@-3{{refers into}} +#else + static_assert(unsigned(i) == I); +#endif +}; + +Toto<2> b; +#if __cplusplus < 202002L +// expected-note@-2{{in instantiation}} +#endif + +} // namespace Seven + +namespace Eight { +struct Kevin { + enum class B { a }; + enum a {}; +}; + +using Kevin::B::a; +#if __cplusplus < 202002L +// expected-warning@-2{{a C++20 extension}} +// expected-error@-3{{using declaration cannot refer to class member}} +// expected-note@-4{{use a constexpr variable instead}} +#endif +using Kevin::B::a; +#if __cplusplus < 202002L +// expected-warning@-2{{a C++20 extension}} +// expected-error@-3{{using declaration cannot refer to class member}} +// expected-note@-4{{use a constexpr variable instead}} +#endif + +class X : Kevin { + using Kevin::B::a; // expected-note{{previous using declaration}} +#if __cplusplus < 202002L +// expected-warning@-2{{a C++20 extension}} +#endif + using Kevin::a; + using Kevin::B::a; // expected-error{{redeclaration of using declaration}} +}; + +} // namespace Eight + +namespace Nine { +namespace Q { +enum class Bob { a }; +using Bob::a; +#if __cplusplus < 202002L +// expected-warning@-2{{a C++20 extension}} +#endif +} // namespace Q + +using Q::a; +using Q::Bob::a; +#if __cplusplus < 202002L +// expected-warning@-2{{a C++20 extension}} +#endif + +#if __cplusplus >= 202002L +struct Foo { + using Q::a; // expected-note{{previous using declaration}} + using Q::Bob::a; + using Q::a; // expected-error{{redeclaration of using declaration}} +}; +#endif +} // namespace Nine diff --git a/clang/test/CXX/dcl.dcl/basic.namespace/namespace.udecl/p7.cpp b/clang/test/CXX/dcl.dcl/basic.namespace/namespace.udecl/p7.cpp --- a/clang/test/CXX/dcl.dcl/basic.namespace/namespace.udecl/p7.cpp +++ b/clang/test/CXX/dcl.dcl/basic.namespace/namespace.udecl/p7.cpp @@ -1,4 +1,11 @@ // RUN: %clang_cc1 -std=c++11 -verify %s +// RUN: %clang_cc1 -std=c++17 -verify %s +// RUN: %clang_cc1 -std=c++20 -verify %s enum class EC { ec }; -using EC::ec; // expected-error {{using declaration cannot refer to a scoped enumerator}} +using EC::ec; +#if __cplusplus < 202002 +// expected-warning@-2 {{using declaration naming a scoped enumerator is a C++20 extension}} +#else +// expected-no-diagnostics +#endif diff --git a/clang/test/SemaCXX/enum-scoped.cpp b/clang/test/SemaCXX/enum-scoped.cpp --- a/clang/test/SemaCXX/enum-scoped.cpp +++ b/clang/test/SemaCXX/enum-scoped.cpp @@ -301,8 +301,8 @@ int E::*p; // expected-error {{does not point into a class}} using E::f; // expected-error {{no member named 'f'}} - using E::a; // expected-error {{using declaration cannot refer to a scoped enumerator}} - E b = a; // expected-error {{undeclared}} + using E::a; // expected-warning {{using declaration naming a scoped enumerator is a C++20 extension}} + E b = a; } namespace test11 {