This is an archive of the discontinued LLVM Phabricator instance.

[clang][C++20] Non-dependent access checks in requires expression.
AbandonedPublic

Authored by usaxena95 on Jan 3 2023, 1:33 AM.

Details

Reviewers
erichkeane
shafik
ilya-biryukov
Group Reviewers
Restricted Project
Summary

Access to members in a non-dependent context would always yield an
invalid expression. When it appears in a requires-expression, then this
is a hard error as this would always result in a substitution failure.

https://eel.is/c++draft/expr.prim.req#general-note-1
Note 1: If a requires-expression contains invalid types or expressions in its requirements, and it does not appear within the declaration of a templated entity, then the program is ill-formed. — end note]
If the substitution of template arguments into a requirement would always result in a substitution failure, the program is ill-formed; no diagnostic required.

Fixes: https://github.com/llvm/llvm-project/issues/53334

Diff Detail

Event Timeline

usaxena95 created this revision.Jan 3 2023, 1:33 AM
Herald added a project: Restricted Project. · View Herald TranscriptJan 3 2023, 1:33 AM
usaxena95 requested review of this revision.Jan 3 2023, 1:33 AM
Herald added a project: Restricted Project. · View Herald TranscriptJan 3 2023, 1:33 AM
Herald added a subscriber: cfe-commits. · View Herald Transcript
usaxena95 added a reviewer: Restricted Project.
usaxena95 updated this revision to Diff 485939.Jan 3 2023, 1:44 AM

More tests.

erichkeane accepted this revision.Jan 3 2023, 6:48 AM
This revision is now accepted and ready to land.Jan 3 2023, 6:48 AM

Should access checks should happen in the context where concept is written or where it's used? Is there a standard wording around it?
If access-checking should happen where concept is defined, having a hard error seems fine because of the wording you quoted:

If the substitution of template arguments into a requirement would always result in a substitution failure, the program is ill-formed; no diagnostic required.

The program is ill-formed and we show the diagnostic even though it's not required.

I poked around and found an interesting case where GCC seems to be doing the wrong thing:

template <class> struct B;
class A {
   static void f();
   friend struct B<short>;
};
 
template <class T> struct B {
    static constexpr int index() requires requires{ A::f(); } {
        return 1;
    }
    static constexpr int index() {
        return 2;
    }
};

static_assert(B<short>::index() == 1); // GCC picks 1, MSVC picks 1.
static_assert(B<int>::index() == 2);   // GCC (incorrectly?) picks 1, MSVC picks 2!

Is this related to this change? Could we add a test that validates Clang is doing the right thing?

clang/lib/Parse/ParseExprCXX.cpp
3510

Could you explain how this changes behaviour and how it leads to fixing the issue?

I'm sure there is quite a bit of thought and debugging behind this one-line change, but it's not evident by just looking at it how it solves the issue.

Should access checks should happen in the context where concept is written or where it's used? Is there a standard wording around it?

Unfortunately, only a standard wording issue :(
https://cplusplus.github.io/CWG/issues/2589.html

We're also allowed to cache atomic constraints regardless of the their (unspecified) scope, which makes this a bit more complicated.

There's some discussion about this in https://github.com/llvm/llvm-project/issues/58672

usaxena95 marked an inline comment as done.Jan 4 2023, 6:01 AM

Should access checks should happen in the context where concept is written or where it's used?
If access-checking should happen where concept is defined, having a hard error seems fine because of the wording you quoted:

As Roy indicated, this is a wording issue. For the time being, concepts are not evaluated in the scope of its use (as the result of atomic constraints should be same at all points of program). An independent requires clause on the other hand considers the scope where it is written.

If the substitution of template arguments into a requirement would always result in a substitution failure, the program is ill-formed; no diagnostic required.

The program is ill-formed and we show the diagnostic even though it's not required.

Are you suggesting to drop the diagnostic in this case ? I think

I poked around and found an interesting case where GCC seems to be doing the wrong thing:

template <class> struct B;
class A {
   static void f();
   friend struct B<short>;
};
 
template <class T> struct B {
    static constexpr int index() requires requires{ A::f(); } {
        return 1;
    }
    static constexpr int index() {
        return 2;
    }
};

static_assert(B<short>::index() == 1); // GCC picks 1, MSVC picks 1.
static_assert(B<int>::index() == 2);   // GCC (incorrectly?) picks 1, MSVC picks 2!

Is this related to this change? Could we add a test that validates Clang is doing the right thing?

This is an interesting test case. Clang does the right thing here and it is related to both this change and the base change https://reviews.llvm.org/D140547.
The test case mentioned on that patch also needs both the changes and also something else.

I will squash both these patches into one for a better review/commit.

clang/lib/Parse/ParseExprCXX.cpp
3510

Sorry for not providing more context here. I agree this makes things work quite unexpectedly.

The main issue here is the delaying of the diagnostics. Eg in Parser::ParseTemplateDeclarationOrSpecialization through ParsingDeclRAIIObject ParsingTemplateParams. Diagnostics in ParseConceptDefinition are attached to Diagnostics pool of ParsingTemplateParams which are never flushed. Creating a new context does not delay the diagnostics.

usaxena95 abandoned this revision.Jan 4 2023, 6:09 AM
usaxena95 marked an inline comment as done.

Moving the changes to https://reviews.llvm.org/D140547