diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index e0e86af257a195..708be0e750af2a 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -58,7 +58,8 @@ C++ Specific Potentially Breaking Changes versions of clang. The deprecation warning for the negative spelling can be disabled with `-Wno-deprecated-no-relaxed-template-template-args`. -- Clang now rejects pointer to member from parenthesized expression in unevaluated context such as ``decltype(&(foo::bar))``. (#GH40906). +- Clang no longer tries to form pointer-to-members from qualified and parenthesized unevaluated expressions + such as ``decltype(&(foo::bar))``. (#GH40906). - Clang now performs semantic analysis for unary operators with dependent operands that are known to be of non-class non-enumeration type prior to instantiation. diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index d60f32674ca3a6..0d6af644f1539d 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -7596,9 +7596,6 @@ def err_nested_non_static_member_use : Error< def warn_cxx98_compat_non_static_member_use : Warning< "use of non-static data member %0 in an unevaluated context is " "incompatible with C++98">, InGroup, DefaultIgnore; -def err_form_ptr_to_member_from_parenthesized_expr : Error< - "cannot form pointer to member from a parenthesized expression; " - "did you mean to remove the parentheses?">; def err_invalid_incomplete_type_use : Error< "invalid use of incomplete type %0">; def err_builtin_func_cast_more_than_one_arg : Error< diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 8d24e34520e778..1a441d99515f48 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -14117,7 +14117,14 @@ QualType Sema::CheckAddressOfOperand(ExprResult &OrigOp, SourceLocation OpLoc) { // Okay: we can take the address of a field. // Could be a pointer to member, though, if there is an explicit // scope qualifier for the class. - if (isa(op) && cast(op)->getQualifier()) { + + // [C++26] [expr.prim.id.general] + // If an id-expression E denotes a non-static non-type member + // of some class C [...] and if E is a qualified-id, E is + // not the un-parenthesized operand of the unary & operator [...] + // the id-expression is transformed into a class member access expression. + if (isa(op) && cast(op)->getQualifier() && + !isa(OrigOp.get())) { DeclContext *Ctx = dcl->getDeclContext(); if (Ctx && Ctx->isRecord()) { if (dcl->getType()->isReferenceType()) { @@ -14127,22 +14134,6 @@ QualType Sema::CheckAddressOfOperand(ExprResult &OrigOp, SourceLocation OpLoc) { return QualType(); } - // C++11 [expr.unary.op] p4: - // A pointer to member is only formed when an explicit & is used and - // its operand is a qualified-id not enclosed in parentheses. - if (isa(OrigOp.get())) { - SourceLocation LeftParenLoc = OrigOp.get()->getBeginLoc(), - RightParenLoc = OrigOp.get()->getEndLoc(); - - Diag(LeftParenLoc, - diag::err_form_ptr_to_member_from_parenthesized_expr) - << SourceRange(OpLoc, RightParenLoc) - << FixItHint::CreateRemoval(LeftParenLoc) - << FixItHint::CreateRemoval(RightParenLoc); - - // Continuing might lead to better error recovery. - } - while (cast(Ctx)->isAnonymousStructOrUnion()) Ctx = Ctx->getParent(); diff --git a/clang/test/CXX/expr/expr.unary/expr.unary.op/p4.cpp b/clang/test/CXX/expr/expr.unary/expr.unary.op/p4.cpp index 162d59439d08ee..170ca0a3f1c6b0 100644 --- a/clang/test/CXX/expr/expr.unary/expr.unary.op/p4.cpp +++ b/clang/test/CXX/expr/expr.unary/expr.unary.op/p4.cpp @@ -43,18 +43,36 @@ namespace test2 { } namespace GH40906 { - struct A { - int val; - void func() {} - }; +struct S { + int x; + void func(); + static_assert(__is_same_as(decltype((S::x)), int&), ""); + static_assert(__is_same_as(decltype(&(S::x)), int*), ""); - void test() { - decltype(&(A::val)) ptr1; // expected-error {{cannot form pointer to member from a parenthesized expression; did you mean to remove the parentheses?}} - int A::* ptr2 = &(A::val); // expected-error {{invalid use of non-static data member 'val'}} + // FIXME: provide better error messages + static_assert(__is_same_as(decltype((S::func)), int&), ""); // expected-error {{call to non-static member function without an object argument}} + static_assert(__is_same_as(decltype(&(S::func)), int*), ""); // expected-error {{call to non-static member function without an object argument}} +}; +static_assert(__is_same_as(decltype((S::x)), int&), ""); +static_assert(__is_same_as(decltype(&(S::x)), int*), ""); +static_assert(__is_same_as(decltype((S::func)), int&), ""); // expected-error {{call to non-static member function without an object argument}} +static_assert(__is_same_as(decltype(&(S::func)), int*), ""); // expected-error {{call to non-static member function without an object argument}} + +struct A { int x;}; + +char q(int *); +short q(int A::*); + +template +constexpr int f(char (*)[sizeof(q(&T::x))]) { return 1; } + +template +constexpr int f(char (*)[sizeof(q(&(T::x)))]) { return 2; } + +constexpr int g(char (*p)[sizeof(char)] = 0) { return f(p); } +constexpr int h(char (*p)[sizeof(short)] = 0) { return f(p); } + +static_assert(g() == 2); +static_assert(h() == 1); - // FIXME: Error messages in these cases are less than clear, we can do - // better. - int size = sizeof(&(A::func)); // expected-error {{call to non-static member function without an object argument}} - void (A::* ptr3)() = &(A::func); // expected-error {{call to non-static member function without an object argument}} - } }