urnathan created this revision. urnathan added reviewers: rsmith, Quuxplusone, bruno. urnathan requested review of this revision. Herald added a project: clang. Herald added a subscriber: cfe-commits.
This adds support for p1099's 'using SCOPED_ENUM::MEMNER;' functionality, bringing a member of an enumerator into the current scope. The novel feature here, is that there need not be a class hierarchical relationship between the current scope and the scope of the SCOPED_ENUM. That's a new thing, the closest equivalent is a typedef or alias declaration. But this means that Sema::CheckUsingDeclQualifier needs adjustment. (a) one can't call it until one knows the set of decls that are being referenced -- if exactly one is an enumerator, we're in the new territory. Thus it needs calling later in some cases. Also (b) there are two ways we hold the set of such decls. During parsing (or instantiating a dependent scope) we have a lookup result, and during instantiation we have a set of shadow decls. Thus two optional arguments, at most one of which should be non-null. The change to clang/test/CXX/dcl.dcl/basic.namespace/namespace.udecl/p3.cpp is to silence an uninteresting access error that the above change causes. Perhaps deferring the scope check until after construction of the shadow-decls in the parsing case would be preferred? Also moving up the check for naming a scoped-enumerator member might be more helpful? Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D100276 Files: clang/include/clang/Basic/DiagnosticSemaKinds.td clang/include/clang/Sema/Sema.h clang/lib/Sema/SemaDeclCXX.cpp clang/lib/Sema/SemaTemplateInstantiateDecl.cpp clang/test/CXX/dcl.dcl/basic.namespace/namespace.udecl/p3.cpp clang/test/CXX/dcl.dcl/basic.namespace/namespace.udecl/p7-cxx20.cpp
Index: clang/test/CXX/dcl.dcl/basic.namespace/namespace.udecl/p7-cxx20.cpp =================================================================== --- /dev/null +++ clang/test/CXX/dcl.dcl/basic.namespace/namespace.udecl/p7-cxx20.cpp @@ -0,0 +1,149 @@ +// RUN: %clang_cc1 -fsyntax-only -std=c++14 -verify %s +// RUN: %clang_cc1 -fsyntax-only -std=c++20 -verify %s + +// p1099 'using SCOPEDENUM::MEMBER;' + +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-error@-2{{using declaration cannot refer to a scoped enumerator}} +#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 One { + +// derived from [namespace.udecl]/3 +enum class button { up, + down }; +struct S { + using button::up; +#if __cplusplus < 202002L + // expected-error@-2{{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-error@-4{{using declaration cannot refer to a scoped enumerator}} +#else +auto a = e4; +auto b = e5; +#endif +} // namespace Three + +namespace Four { + +template <typename T> +struct TPL { + enum class E1 { e1 }; + struct IN { + enum class E2 { e2 }; + }; + +protected: + enum class E3 { e3 }; // expected-note{{declared protected here}} +}; + +using TPL<int>::E1::e1; +#if __cplusplus < 202002L +// expected-error@-2{{cannot refer to a scoped enumerator}} +#else +using TPL<float>::IN::E2::e2; + +auto a = e1; +auto b = e2; +#endif + +enum class E4 { e4 }; +template <typename T> +struct DER : TPL<int> { + using TPL<T>::E1::e1; +#if __cplusplus < 202002L + // expected-error@-2{{'TPL<float>::E1::', which is not a class}} + // expected-error@-3{{'TPL<int>::E1::', which is not a class}} +#endif + using TPL<T>::E3::e3; // expected-error{{is a protected member}} +#if __cplusplus < 202002L + // expected-error@-2{{'TPL<float>::E3::', which is not a class}} + // expected-error@-3{{'TPL<int>::E3::', which is not a class}} +#endif + + using E4::e4; +#if __cplusplus < 202002L + // expected-error@-2{{which is not a class}} +#else + auto Foo() { return e1; } + auto Bar() { return e2; } +#endif +}; + +DER<float> x; // expected-note{{requested here}} +DER<int> y; +#if __cplusplus < 202002L +// expected-note@-2{{requested here}} +#else +auto y1 = y.Foo(); +auto y2 = y.Bar(); +#endif +} // namespace Four Index: clang/test/CXX/dcl.dcl/basic.namespace/namespace.udecl/p3.cpp =================================================================== --- clang/test/CXX/dcl.dcl/basic.namespace/namespace.udecl/p3.cpp +++ clang/test/CXX/dcl.dcl/basic.namespace/namespace.udecl/p3.cpp @@ -17,6 +17,7 @@ }; class C { +public: int g(); }; Index: clang/lib/Sema/SemaTemplateInstantiateDecl.cpp =================================================================== --- clang/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ clang/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -3063,10 +3063,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); @@ -3075,6 +3074,9 @@ 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); Index: clang/lib/Sema/SemaDeclCXX.cpp =================================================================== --- clang/lib/Sema/SemaDeclCXX.cpp +++ clang/lib/Sema/SemaDeclCXX.cpp @@ -12069,12 +12069,14 @@ SS, IdentLoc, Previous)) return nullptr; - // Check for bad qualifiers. - if (CheckUsingDeclQualifier(UsingLoc, HasTypenameKeyword, SS, NameInfo, - IdentLoc)) + DeclContext *LookupContext = computeDeclContext(SS); + + // Check for bad qualifiers. This will not necessarily detect all badness, as + // C++20 permits enumerators to cross the class-heirarchy boundary + if (!LookupContext && CheckUsingDeclQualifier(UsingLoc, HasTypenameKeyword, + SS, NameInfo, IdentLoc)) return nullptr; - DeclContext *LookupContext = computeDeclContext(SS); NamedDecl *D; NestedNameSpecifierLoc QualifierLoc = SS.getWithLocInContext(Context); if (!LookupContext || EllipsisLoc.isValid()) { @@ -12128,12 +12130,18 @@ LookupQualifiedName(R, LookupContext); + // Validate the context, now we have a lookup + if (CheckUsingDeclQualifier(UsingLoc, HasTypenameKeyword, SS, NameInfo, + IdentLoc, &R)) + return nullptr; + // Try to correct typos if possible. If constructor name lookup finds no // results, that means the named class has no explicit constructors, and we // suppressed declaring implicit ones (probably because it's dependent or // invalid). if (R.empty() && NameInfo.getName().getNameKind() != DeclarationName::CXXConstructorName) { + // FIXME: 2021-04-07: When was this hack needed? Can it be deleted now? // HACK: Work around a bug in libstdc++'s detection of ::gets. Sometimes // it will believe that glibc provides a ::gets in cases where it does not, // and will try to pull it into namespace std with a using-declaration. @@ -12228,13 +12236,16 @@ return BuildInvalid(); } - // C++14 [namespace.udecl]p7: - // A using-declaration shall not name a scoped enumerator. - if (auto *ED = R.getAsSingle<EnumConstantDecl>()) { - if (cast<EnumDecl>(ED->getDeclContext())->isScoped()) { - Diag(IdentLoc, diag::err_using_decl_can_not_refer_to_scoped_enum) - << SS.getRange(); - return BuildInvalid(); + if (!getLangOpts().CPlusPlus20) { + // C++14 [namespace.udecl]p7: + // A using-declaration shall not name a scoped enumerator. + // C++20 p1099 permits enumerators. + if (auto *ED = R.getAsSingle<EnumConstantDecl>()) { + if (cast<EnumDecl>(ED->getDeclContext())->isScoped()) { + Diag(IdentLoc, diag::err_cxx14_using_decl_can_not_refer_to_scoped_enum) + << SS.getRange(); + return BuildInvalid(); + } } } @@ -12377,48 +12388,62 @@ 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"); + + if (getLangOpts().CPlusPlus20) { + // There could be a type-tag and an enum. There must only be an enum + EnumConstantDecl *ED = nullptr; + if (R) + ED = R->getAsSingle<EnumConstantDecl>(); + else if (UD && UD->shadow_size() == 1) + ED = dyn_cast<EnumConstantDecl>(UD->shadow_begin()->getTargetDecl()); + + if (ED) + // Naming an enumerator is ok, regardless of heirarchy. We check + // accessibility elsewhere. + return false; + } 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<CXXRecordDecl>(NamedContext->getRedeclContext()) - : nullptr; - if (RD && RequireCompleteDeclContext(const_cast<CXXScopeSpec&>(SS), RD)) - RD = nullptr; - + if (NamedContext ? NamedContext->getRedeclContext()->isRecord() + : HasTypename) { Diag(NameLoc, 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) + if (!NamedContext) return true; - // Find what this using-declaration was referring to. - LookupResult R(*this, NameInfo, LookupOrdinaryName); - R.setHideTags(false); - R.suppressDiagnostics(); - LookupQualifiedName(R, RD); + auto *RD = cast<CXXRecordDecl>(NamedContext->getRedeclContext()); + if (RequireCompleteDeclContext(const_cast<CXXScopeSpec &>(SS), RD)) + return true; - if (R.getAsSingle<TypeDecl>()) { + if (!R) { + // We will have already diagnosed the problem on the template + // definition, Maybe we should do so again? + } else if (R->getAsSingle<TypeDecl>()) { if (getLangOpts().CPlusPlus11) { // Convert 'using X::Y;' to 'using Y = X::Y;'. Diag(SS.getBeginLoc(), diag::note_using_decl_class_member_workaround) @@ -12435,7 +12460,7 @@ << FixItHint::CreateInsertion( InsertLoc, " " + NameInfo.getName().getAsString()); } - } else if (R.getAsSingle<VarDecl>()) { + } else if (R->getAsSingle<VarDecl>()) { // 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; @@ -12448,7 +12473,7 @@ Diag(UsingLoc, diag::note_using_decl_class_member_workaround) << 2 // reference declaration << FixIt; - } else if (R.getAsSingle<EnumConstantDecl>()) { + } else if (R->getAsSingle<EnumConstantDecl>()) { // 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. @@ -12467,12 +12492,10 @@ return true; } - // Otherwise, this might be valid. + // Otherwise ok return false; } - // 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 @@ -12484,6 +12507,8 @@ 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. Index: clang/include/clang/Sema/Sema.h =================================================================== --- clang/include/clang/Sema/Sema.h +++ clang/include/clang/Sema/Sema.h @@ -5673,11 +5673,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, Index: clang/include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- clang/include/clang/Basic/DiagnosticSemaKinds.td +++ clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -538,8 +538,8 @@ "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 err_cxx14_using_decl_can_not_refer_to_scoped_enum : Error< + "using declaration cannot refer to a scoped enumerator before C++20">; def err_using_decl_constructor : Error< "using declaration cannot refer to a constructor">; def warn_cxx98_compat_using_decl_constructor : Warning<
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits