[clang] p1099 using enum part 1

This adds support for p1099's 'using SCOPED_ENUM::MEMBER;'
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.

Differential Revision: https://reviews.llvm.org/D100276
This commit is contained in:
Nathan Sidwell 2021-05-04 07:59:17 -07:00
parent 09e92c607c
commit 012898b92c
8 changed files with 407 additions and 69 deletions

View File

@ -536,6 +536,10 @@ def err_using_dependent_value_is_type : Error<
"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<CXXPre20Compat>,
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 @@ def err_using_decl_constructor_not_in_direct_base : Error<
"%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<CXXPre20Compat>, DefaultIgnore;
def ext_using_decl_class_member_enumerator : ExtWarn<
"member using declaration naming a non-member enumerator is "
"a C++20 extension">, InGroup<CXX20>;
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 @@ def note_using_decl_class_member_workaround : Note<
"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<CXXPre20Compat>, DefaultIgnore;
def ext_using_decl_scoped_enumerator : ExtWarn<
"using declaration naming a scoped enumerator is a C++20 extension">,
InGroup<CXX20>;
def err_using_decl_constructor : Error<
"using declaration cannot refer to a constructor">;
def warn_cxx98_compat_using_decl_constructor : Warning<

View File

@ -5714,11 +5714,12 @@ public:
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,

View File

@ -11659,7 +11659,7 @@ bool Sema::CheckUsingShadowDecl(BaseUsingDecl *BUD, NamedDecl *Orig,
// 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<T>.
// We can't diagnose it immediately because C<T> is an unknown
// specialization. The UsingShadowDecl in D<T> then points directly
// specialization. The UsingShadowDecl in D<T> 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 @@ NamedDecl *Sema::BuildUsingDeclaration(
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 @@ NamedDecl *Sema::BuildUsingDeclaration(
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 @@ NamedDecl *Sema::BuildUsingDeclaration(
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 @@ NamedDecl *Sema::BuildUsingDeclaration(
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();
}
}
UsingDecl *UD = BuildValid();
// Some additional rules apply to inheriting constructors.
@ -12410,48 +12405,83 @@ bool Sema::CheckUsingDeclRedeclaration(SourceLocation UsingLoc,
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<EnumConstantDecl>();
else if (UD && UD->shadow_size() == 1)
EC = dyn_cast<EnumConstantDecl>(UD->shadow_begin()->getTargetDecl());
if (EC)
Cxx20Enumerator = getLangOpts().CPlusPlus20;
if (auto *ED = dyn_cast<EnumDecl>(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<CXXRecordDecl>(NamedContext->getRedeclContext())
: nullptr;
if (RD && RequireCompleteDeclContext(const_cast<CXXScopeSpec&>(SS), RD))
RD = nullptr;
// 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, diag::err_using_decl_can_not_refer_to_class_member)
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<CXXRecordDecl>(NamedContext->getRedeclContext())
: nullptr;
if (RD && !RequireCompleteDeclContext(const_cast<CXXScopeSpec &>(SS), RD)) {
// See if there's a helpful fixit
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)
@ -12468,7 +12498,7 @@ bool Sema::CheckUsingDeclQualifier(SourceLocation UsingLoc,
<< 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;
@ -12481,7 +12511,7 @@ bool Sema::CheckUsingDeclQualifier(SourceLocation UsingLoc,
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.
@ -12497,15 +12527,11 @@ bool Sema::CheckUsingDeclQualifier(SourceLocation UsingLoc,
<< (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 @@ bool Sema::CheckUsingDeclQualifier(SourceLocation UsingLoc,
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 @@ bool Sema::CheckUsingDeclQualifier(SourceLocation UsingLoc,
if (cast<CXXRecordDecl>(CurContext)->isProvablyNotDerivedFrom(
cast<CXXRecordDecl>(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<CXXRecordDecl>(NamedContext)->isInvalidDecl()) {
Diag(SS.getRange().getBegin(),
Diag(SS.getBeginLoc(),
diag::err_using_decl_nested_name_specifier_is_not_base_class)
<< SS.getScopeRep()
<< cast<CXXRecordDecl>(CurContext)
<< SS.getRange();
<< SS.getScopeRep() << cast<CXXRecordDecl>(CurContext)
<< SS.getRange();
}
return true;
}

View File

@ -3067,10 +3067,9 @@ Decl *TemplateDeclInstantiator::VisitUsingDecl(UsingDecl *D) {
}
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 @@ Decl *TemplateDeclInstantiator::VisitUsingDecl(UsingDecl *D) {
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);

View File

@ -17,6 +17,7 @@ struct B {
};
class C {
public:
int g();
};
@ -42,7 +43,7 @@ class D2 : public B {
#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 {

View File

@ -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 <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-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<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-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<T>::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<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
namespace Five {
template <unsigned I, unsigned K>
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 <unsigned I, unsigned K>
struct Quux {
enum class Q : unsigned; // expected-note{{member is declared here}}
enum class R : unsigned { i = I,
k = K };
};
template <unsigned I> struct Fido {
using Quux<I, I>::Q::nothing; // expected-error{{implicit instantiation of undefined}}
};
Fido<2> a; // expected-note{{in instantiation}}
} // namespace Six
namespace Seven {
template <unsigned I, unsigned K>
struct Quux {
enum class R : unsigned { i = I,
k = K };
};
template <unsigned I> struct Toto {
using Quux<I, I>::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

View File

@ -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

View File

@ -301,8 +301,8 @@ namespace PR18044 {
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 {