Skip to content

Commit

Permalink
P0969R0: allow structured binding of accessible members, not only pub…
Browse files Browse the repository at this point in the history
…lic members.

llvm-svn: 343036
  • Loading branch information
zygoloid committed Sep 25, 2018
1 parent 579cf90 commit 5c9b3b7
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 54 deletions.
10 changes: 6 additions & 4 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Expand Up @@ -452,10 +452,12 @@ def err_decomp_decl_multiple_bases_with_members : Error<
"have non-static data members">;
def err_decomp_decl_ambiguous_base : Error<
"cannot decompose members of ambiguous base class %1 of %0:%2">;
def err_decomp_decl_non_public_base : Error<
"cannot decompose members of non-public base class %1 of %0">;
def err_decomp_decl_non_public_member : Error<
"cannot decompose non-public member %0 of %1">;
def err_decomp_decl_inaccessible_base : Error<
"cannot decompose members of inaccessible base class %1 of %0">,
AccessControl;
def err_decomp_decl_inaccessible_field : Error<
"cannot decompose %select{private|protected}0 member %1 of %3">,
AccessControl;
def err_decomp_decl_anon_union_member : Error<
"cannot decompose class type %0 because it has an anonymous "
"%select{struct|union}1 member">;
Expand Down
4 changes: 4 additions & 0 deletions clang/include/clang/Sema/Sema.h
Expand Up @@ -6036,6 +6036,10 @@ class Sema {
AccessResult CheckMemberAccess(SourceLocation UseLoc,
CXXRecordDecl *NamingClass,
DeclAccessPair Found);
AccessResult
CheckStructuredBindingMemberAccess(SourceLocation UseLoc,
CXXRecordDecl *DecomposedClass,
DeclAccessPair Field);
AccessResult CheckMemberOperatorAccess(SourceLocation Loc,
Expr *ObjectExpr,
Expr *ArgExpr,
Expand Down
16 changes: 16 additions & 0 deletions clang/lib/Sema/SemaAccess.cpp
Expand Up @@ -1728,6 +1728,22 @@ Sema::AccessResult Sema::CheckMemberAccess(SourceLocation UseLoc,
return CheckAccess(*this, UseLoc, Entity);
}

/// Checks implicit access to a member in a structured binding.
Sema::AccessResult
Sema::CheckStructuredBindingMemberAccess(SourceLocation UseLoc,
CXXRecordDecl *DecomposedClass,
DeclAccessPair Field) {
if (!getLangOpts().AccessControl ||
Field.getAccess() == AS_public)
return AR_accessible;

AccessTarget Entity(Context, AccessTarget::Member, DecomposedClass, Field,
Context.getRecordType(DecomposedClass));
Entity.setDiag(diag::err_decomp_decl_inaccessible_field);

return CheckAccess(*this, UseLoc, Entity);
}

/// Checks access to an overloaded member operator, including
/// conversion operators.
Sema::AccessResult Sema::CheckMemberOperatorAccess(SourceLocation OpLoc,
Expand Down
8 changes: 8 additions & 0 deletions clang/lib/Sema/SemaDeclAttr.cpp
Expand Up @@ -7514,6 +7514,7 @@ void Sema::PopParsingDeclaration(ParsingDeclState state, Decl *decl) {
// for each of the different declarations.
const DelayedDiagnosticPool *pool = &poppedPool;
do {
bool AnyAccessFailures = false;
for (DelayedDiagnosticPool::pool_iterator
i = pool->pool_begin(), e = pool->pool_end(); i != e; ++i) {
// This const_cast is a bit lame. Really, Triggered should be mutable.
Expand All @@ -7530,7 +7531,14 @@ void Sema::PopParsingDeclaration(ParsingDeclState state, Decl *decl) {
break;

case DelayedDiagnostic::Access:
// Only produce one access control diagnostic for a structured binding
// declaration: we don't need to tell the user that all the fields are
// inaccessible one at a time.
if (AnyAccessFailures && isa<DecompositionDecl>(decl))
continue;
HandleDelayedAccessCheck(diag, decl);
if (diag.Triggered)
AnyAccessFailures = true;
break;

case DelayedDiagnostic::ForbiddenType:
Expand Down
71 changes: 27 additions & 44 deletions clang/lib/Sema/SemaDeclCXX.cpp
Expand Up @@ -1227,16 +1227,16 @@ static bool checkTupleLikeDecomposition(Sema &S,
/// Find the base class to decompose in a built-in decomposition of a class type.
/// This base class search is, unfortunately, not quite like any other that we
/// perform anywhere else in C++.
static const CXXRecordDecl *findDecomposableBaseClass(Sema &S,
SourceLocation Loc,
const CXXRecordDecl *RD,
CXXCastPath &BasePath) {
static DeclAccessPair findDecomposableBaseClass(Sema &S, SourceLocation Loc,
const CXXRecordDecl *RD,
CXXCastPath &BasePath) {
auto BaseHasFields = [](const CXXBaseSpecifier *Specifier,
CXXBasePath &Path) {
return Specifier->getType()->getAsCXXRecordDecl()->hasDirectFields();
};

const CXXRecordDecl *ClassWithFields = nullptr;
AccessSpecifier AS = AS_public;
if (RD->hasDirectFields())
// [dcl.decomp]p4:
// Otherwise, all of E's non-static data members shall be public direct
Expand All @@ -1249,7 +1249,7 @@ static const CXXRecordDecl *findDecomposableBaseClass(Sema &S,
if (!RD->lookupInBases(BaseHasFields, Paths)) {
// If no classes have fields, just decompose RD itself. (This will work
// if and only if zero bindings were provided.)
return RD;
return DeclAccessPair::make(const_cast<CXXRecordDecl*>(RD), AS_public);
}

CXXBasePath *BestPath = nullptr;
Expand All @@ -1262,7 +1262,7 @@ static const CXXRecordDecl *findDecomposableBaseClass(Sema &S,
S.Diag(Loc, diag::err_decomp_decl_multiple_bases_with_members)
<< false << RD << BestPath->back().Base->getType()
<< P.back().Base->getType();
return nullptr;
return DeclAccessPair();
} else if (P.Access < BestPath->Access) {
BestPath = &P;
}
Expand All @@ -1273,23 +1273,13 @@ static const CXXRecordDecl *findDecomposableBaseClass(Sema &S,
if (Paths.isAmbiguous(S.Context.getCanonicalType(BaseType))) {
S.Diag(Loc, diag::err_decomp_decl_ambiguous_base)
<< RD << BaseType << S.getAmbiguousPathsDisplayString(Paths);
return nullptr;
return DeclAccessPair();
}

// ... public base class of E.
if (BestPath->Access != AS_public) {
S.Diag(Loc, diag::err_decomp_decl_non_public_base)
<< RD << BaseType;
for (auto &BS : *BestPath) {
if (BS.Base->getAccessSpecifier() != AS_public) {
S.Diag(BS.Base->getBeginLoc(), diag::note_access_constrained_by_path)
<< (BS.Base->getAccessSpecifier() == AS_protected)
<< (BS.Base->getAccessSpecifierAsWritten() == AS_none);
break;
}
}
return nullptr;
}
// ... [accessible, implied by other rules] base class of E.
S.CheckBaseClassAccess(Loc, BaseType, S.Context.getRecordType(RD),
*BestPath, diag::err_decomp_decl_inaccessible_base);
AS = BestPath->Access;

ClassWithFields = BaseType->getAsCXXRecordDecl();
S.BuildBasePathArray(Paths, BasePath);
Expand All @@ -1302,17 +1292,19 @@ static const CXXRecordDecl *findDecomposableBaseClass(Sema &S,
S.Diag(Loc, diag::err_decomp_decl_multiple_bases_with_members)
<< (ClassWithFields == RD) << RD << ClassWithFields
<< Paths.front().back().Base->getType();
return nullptr;
return DeclAccessPair();
}

return ClassWithFields;
return DeclAccessPair::make(const_cast<CXXRecordDecl*>(ClassWithFields), AS);
}

static bool checkMemberDecomposition(Sema &S, ArrayRef<BindingDecl*> Bindings,
ValueDecl *Src, QualType DecompType,
const CXXRecordDecl *RD) {
const CXXRecordDecl *OrigRD) {
CXXCastPath BasePath;
RD = findDecomposableBaseClass(S, Src->getLocation(), RD, BasePath);
DeclAccessPair BasePair =
findDecomposableBaseClass(S, Src->getLocation(), OrigRD, BasePath);
const CXXRecordDecl *RD = cast_or_null<CXXRecordDecl>(BasePair.getDecl());
if (!RD)
return true;
QualType BaseType = S.Context.getQualifiedType(S.Context.getRecordType(RD),
Expand All @@ -1329,7 +1321,8 @@ static bool checkMemberDecomposition(Sema &S, ArrayRef<BindingDecl*> Bindings,
return true;
};

// all of E's non-static data members shall be public [...] members,
// all of E's non-static data members shall be [...] well-formed
// when named as e.name in the context of the structured binding,
// E shall not have an anonymous union member, ...
unsigned I = 0;
for (auto *FD : RD->fields()) {
Expand All @@ -1347,26 +1340,16 @@ static bool checkMemberDecomposition(Sema &S, ArrayRef<BindingDecl*> Bindings,
if (I >= Bindings.size())
return DiagnoseBadNumberOfBindings();
auto *B = Bindings[I++];

SourceLocation Loc = B->getLocation();
if (FD->getAccess() != AS_public) {
S.Diag(Loc, diag::err_decomp_decl_non_public_member) << FD << DecompType;

// Determine whether the access specifier was explicit.
bool Implicit = true;
for (const auto *D : RD->decls()) {
if (declaresSameEntity(D, FD))
break;
if (isa<AccessSpecDecl>(D)) {
Implicit = false;
break;
}
}

S.Diag(FD->getLocation(), diag::note_access_natural)
<< (FD->getAccess() == AS_protected) << Implicit;
return true;
}
// The field must be accessible in the context of the structured binding.
// We already checked that the base class is accessible.
// FIXME: Add 'const' to AccessedEntity's classes so we can remove the
// const_cast here.
S.CheckStructuredBindingMemberAccess(
Loc, const_cast<CXXRecordDecl *>(OrigRD),
DeclAccessPair::make(FD, CXXRecordDecl::MergeAccess(
BasePair.getAccess(), FD->getAccess())));

// Initialize the binding to Src.FD.
ExprResult E = S.BuildDeclRefExpr(Src, DecompType, VK_LValue, Loc);
Expand Down
51 changes: 46 additions & 5 deletions clang/test/CXX/dcl.decl/dcl.decomp/p4.cpp
Expand Up @@ -20,15 +20,15 @@ namespace NonPublicMembers {
int a; // expected-note 2{{declared private here}}
};

struct NonPublic3 : private A {}; // expected-note {{constrained by private inheritance}}
struct NonPublic3 : private A {}; // expected-note {{declared private here}}

struct NonPublic4 : NonPublic2 {};

void test() {
auto [a1] = NonPublic1(); // expected-error {{cannot decompose non-public member 'a' of 'NonPublicMembers::NonPublic1'}}
auto [a2] = NonPublic2(); // expected-error {{cannot decompose non-public member 'a' of 'NonPublicMembers::NonPublic2'}}
auto [a3] = NonPublic3(); // expected-error {{cannot decompose members of non-public base class 'A' of 'NonPublic3'}}
auto [a4] = NonPublic4(); // expected-error {{cannot decompose non-public member 'a' of 'NonPublicMembers::NonPublic4'}}
auto [a1] = NonPublic1(); // expected-error {{cannot decompose protected member 'a' of 'NonPublicMembers::NonPublic1'}}
auto [a2] = NonPublic2(); // expected-error {{cannot decompose private member 'a' of 'NonPublicMembers::NonPublic2'}}
auto [a3] = NonPublic3(); // expected-error {{cannot decompose members of inaccessible base class 'A' of 'NonPublicMembers::NonPublic3'}}
auto [a4] = NonPublic4(); // expected-error {{cannot decompose private member 'a' of 'NonPublicMembers::NonPublic2'}}
}
}

Expand Down Expand Up @@ -198,3 +198,44 @@ namespace std_example {
same<decltype((x)), const int&> same1;
same<decltype((y)), const volatile double&> same2;
}

namespace p0969r0 {
struct A {
int x;
int y;
};
struct B : private A { // expected-note {{declared private here}}
void test_member() {
auto &[x, y] = *this;
}
friend void test_friend(B);
};
void test_friend(B b) {
auto &[x, y] = b;
}
void test_external(B b) {
auto &[x, y] = b; // expected-error {{cannot decompose members of inaccessible base class 'p0969r0::A' of 'p0969r0::B'}}
}

struct C {
int x;
protected:
int y; // expected-note {{declared protected here}} expected-note {{can only access this member on an object of type 'p0969r0::D'}}
void test_member() {
auto &[x, y] = *this;
}
friend void test_friend(struct D);
};
struct D : C {
static void test_member(D d, C c) {
auto &[x1, y1] = d;
auto &[x2, y2] = c; // expected-error {{cannot decompose protected member 'y' of 'p0969r0::C'}}
}
};
void test_friend(D d) {
auto &[x, y] = d;
}
void test_external(D d) {
auto &[x, y] = d; // expected-error {{cannot decompose protected member 'y' of 'p0969r0::C'}}
}
}
2 changes: 1 addition & 1 deletion clang/www/cxx_status.html
Expand Up @@ -760,7 +760,7 @@ <h2 id="cxx17">C++17 implementation status</h2>
<tr>
<!-- from Jacksonville 2018 -->
<td><a href="http://wg21.link/p0969r0">P0969R0</a> (<a href="#dr">DR</a>)</td>
<td class="none" align="center">No</td>
<td class="svn" align="center">SVN</td>
</tr>
<tr>
<td>Separate variable and condition for <tt>if</tt> and <tt>switch</tt></td>
Expand Down

0 comments on commit 5c9b3b7

Please sign in to comment.