-
Notifications
You must be signed in to change notification settings - Fork 12.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Clang][Sema] Earlier type checking for builtin unary operators #90500
Conversation
@llvm/pr-subscribers-clang Author: Krystian Stasiowski (sdkrystian) ChangesCurrently, clang postpones all semantic analysis of unary operators with operands of pointer/pointer to member/array/function type until instantiation whenever that type is dependent (e.g. This patch moves the point at which we perform semantic analysis for such expression to be prior to instantiation. Full diff: https://github.com/llvm/llvm-project/pull/90500.diff 9 Files Affected:
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index dff02d4861b3db..471f78bbdf283c 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -8040,7 +8040,11 @@ inline bool Type::isUndeducedType() const {
/// Determines whether this is a type for which one can define
/// an overloaded operator.
inline bool Type::isOverloadableType() const {
- return isDependentType() || isRecordType() || isEnumeralType();
+ QualType Canonical = getCanonicalTypeInternal();
+ if (!Canonical->isDependentType())
+ return isRecordType() || isEnumeralType();
+ return !isArrayType() && !isFunctionType() && !isAnyPointerType() &&
+ !isMemberPointerType();
}
/// Determines whether this type is written as a typedef-name.
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 50f92c496a539a..9284ccb3eb7715 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -671,11 +671,12 @@ ExprResult Sema::DefaultLvalueConversion(Expr *E) {
// We don't want to throw lvalue-to-rvalue casts on top of
// expressions of certain types in C++.
- if (getLangOpts().CPlusPlus &&
- (E->getType() == Context.OverloadTy ||
- T->isDependentType() ||
- T->isRecordType()))
- return E;
+ if (getLangOpts().CPlusPlus) {
+ if (T == Context.OverloadTy || T->isRecordType() ||
+ (T->isDependentType() && !T->isAnyPointerType() &&
+ !T->isMemberPointerType()))
+ return E;
+ }
// The C standard is actually really unclear on this point, and
// DR106 tells us what the result should be but not why. It's
@@ -11116,7 +11117,7 @@ static bool checkArithmeticIncompletePointerType(Sema &S, SourceLocation Loc,
if (const AtomicType *ResAtomicType = ResType->getAs<AtomicType>())
ResType = ResAtomicType->getValueType();
- assert(ResType->isAnyPointerType() && !ResType->isDependentType());
+ assert(ResType->isAnyPointerType());
QualType PointeeTy = ResType->getPointeeType();
return S.RequireCompleteSizedType(
Loc, PointeeTy,
@@ -14287,7 +14288,9 @@ static QualType CheckIncrementDecrementOperand(Sema &S, Expr *Op,
ExprObjectKind &OK,
SourceLocation OpLoc,
bool IsInc, bool IsPrefix) {
- if (Op->isTypeDependent())
+ if (Op->isTypeDependent() &&
+ (Op->hasPlaceholderType() ||
+ Op->getType()->isSpecificBuiltinType(BuiltinType::Dependent)))
return S.Context.DependentTy;
QualType ResType = Op->getType();
@@ -14725,7 +14728,9 @@ static void RecordModifiableNonNullParam(Sema &S, const Expr *Exp) {
static QualType CheckIndirectionOperand(Sema &S, Expr *Op, ExprValueKind &VK,
SourceLocation OpLoc,
bool IsAfterAmp = false) {
- if (Op->isTypeDependent())
+ if (Op->isTypeDependent() &&
+ (Op->hasPlaceholderType() ||
+ Op->getType()->isSpecificBuiltinType(BuiltinType::Dependent)))
return S.Context.DependentTy;
ExprResult ConvResult = S.UsualUnaryConversions(Op);
diff --git a/clang/test/AST/ast-dump-expr-json.cpp b/clang/test/AST/ast-dump-expr-json.cpp
index 0fb07b0b434cc3..4b7365e554cb7c 100644
--- a/clang/test/AST/ast-dump-expr-json.cpp
+++ b/clang/test/AST/ast-dump-expr-json.cpp
@@ -4261,9 +4261,9 @@ void TestNonADLCall3() {
// CHECK-NEXT: }
// CHECK-NEXT: },
// CHECK-NEXT: "type": {
-// CHECK-NEXT: "qualType": "<dependent type>"
+// CHECK-NEXT: "qualType": "V"
// CHECK-NEXT: },
-// CHECK-NEXT: "valueCategory": "prvalue",
+// CHECK-NEXT: "valueCategory": "lvalue",
// CHECK-NEXT: "isPostfix": false,
// CHECK-NEXT: "opcode": "*",
// CHECK-NEXT: "canOverflow": false,
diff --git a/clang/test/AST/ast-dump-expr.cpp b/clang/test/AST/ast-dump-expr.cpp
index 69e65e22d61d0d..4df5ba4276abd2 100644
--- a/clang/test/AST/ast-dump-expr.cpp
+++ b/clang/test/AST/ast-dump-expr.cpp
@@ -282,7 +282,7 @@ void PrimaryExpressions(Ts... a) {
// CHECK-NEXT: CompoundStmt
// CHECK-NEXT: FieldDecl 0x{{[^ ]*}} <col:8> col:8 implicit 'V'
// CHECK-NEXT: ParenListExpr 0x{{[^ ]*}} <col:8> 'NULL TYPE'
- // CHECK-NEXT: UnaryOperator 0x{{[^ ]*}} <col:8> '<dependent type>' prefix '*' cannot overflow
+ // CHECK-NEXT: UnaryOperator 0x{{[^ ]*}} <col:8> 'V' lvalue prefix '*' cannot overflow
// CHECK-NEXT: CXXThisExpr 0x{{[^ ]*}} <col:8> 'V *' this
}
};
diff --git a/clang/test/AST/ast-dump-lambda.cpp b/clang/test/AST/ast-dump-lambda.cpp
index ef8789cd97d3e7..a4d3fe4fbda57c 100644
--- a/clang/test/AST/ast-dump-lambda.cpp
+++ b/clang/test/AST/ast-dump-lambda.cpp
@@ -81,7 +81,7 @@ template <typename... Ts> void test(Ts... a) {
// CHECK-NEXT: | | | `-CompoundStmt {{.*}} <col:15, col:16>
// CHECK-NEXT: | | `-FieldDecl {{.*}} <col:8> col:8{{( imported)?}} implicit 'V'
// CHECK-NEXT: | |-ParenListExpr {{.*}} <col:8> 'NULL TYPE'
-// CHECK-NEXT: | | `-UnaryOperator {{.*}} <col:8> '<dependent type>' prefix '*' cannot overflow
+// CHECK-NEXT: | | `-UnaryOperator {{.*}} <col:8> 'V' lvalue prefix '*' cannot overflow
// CHECK-NEXT: | | `-CXXThisExpr {{.*}} <col:8> 'V *' this
// CHECK-NEXT: | `-CompoundStmt {{.*}} <col:15, col:16>
// CHECK-NEXT: |-DeclStmt {{.*}} <line:22:3, col:11>
diff --git a/clang/test/CXX/over/over.built/ast.cpp b/clang/test/CXX/over/over.built/ast.cpp
index 56a63431269f30..b95a453de90222 100644
--- a/clang/test/CXX/over/over.built/ast.cpp
+++ b/clang/test/CXX/over/over.built/ast.cpp
@@ -4,15 +4,17 @@ struct A{};
template <typename T, typename U>
auto Test(T* pt, U* pu) {
- // CHECK: UnaryOperator {{.*}} '<dependent type>' lvalue prefix '*'
+ // CHECK: UnaryOperator {{.*}} 'T' lvalue prefix '*'
+ // CHECK-NEXT: ImplicitCastExpr {{.*}} 'T *' <LValueToRValue>
// CHECK-NEXT: DeclRefExpr {{.*}} 'T *' lvalue ParmVar {{.*}} 'pt' 'T *'
(void)*pt;
- // CHECK: UnaryOperator {{.*}} '<dependent type>' lvalue prefix '++'
+ // CHECK: UnaryOperator {{.*}} 'T *' lvalue prefix '++'
// CHECK-NEXT: DeclRefExpr {{.*}} 'T *' lvalue ParmVar {{.*}} 'pt' 'T *'
(void)(++pt);
- // CHECK: UnaryOperator {{.*}} '<dependent type>' prefix '+'
+ // CHECK: UnaryOperator {{.*}} 'T *' prefix '+'
+ // CHECK-NEXT: ImplicitCastExpr {{.*}} 'T *' <LValueToRValue>
// CHECK-NEXT: DeclRefExpr {{.*}} 'T *' lvalue ParmVar {{.*}} 'pt' 'T *'
(void)(+pt);
diff --git a/clang/test/Frontend/noderef_templates.cpp b/clang/test/Frontend/noderef_templates.cpp
index 5fde6efd87c7fb..9e54cd5d788992 100644
--- a/clang/test/Frontend/noderef_templates.cpp
+++ b/clang/test/Frontend/noderef_templates.cpp
@@ -3,8 +3,8 @@
#define NODEREF __attribute__((noderef))
template <typename T>
-int func(T NODEREF *a) { // expected-note 2 {{a declared here}}
- return *a + 1; // expected-warning 2 {{dereferencing a; was declared with a 'noderef' type}}
+int func(T NODEREF *a) { // expected-note 3 {{a declared here}}
+ return *a + 1; // expected-warning 3 {{dereferencing a; was declared with a 'noderef' type}}
}
void func() {
diff --git a/clang/test/SemaCXX/cxx2b-deducing-this.cpp b/clang/test/SemaCXX/cxx2b-deducing-this.cpp
index 5f29a955e053c3..aa64530bd5be3d 100644
--- a/clang/test/SemaCXX/cxx2b-deducing-this.cpp
+++ b/clang/test/SemaCXX/cxx2b-deducing-this.cpp
@@ -19,7 +19,7 @@ struct S {
// new and delete are implicitly static
void *operator new(this unsigned long); // expected-error{{an explicit object parameter cannot appear in a static function}}
void operator delete(this void*); // expected-error{{an explicit object parameter cannot appear in a static function}}
-
+
void g(this auto) const; // expected-error{{explicit object member function cannot have 'const' qualifier}}
void h(this auto) &; // expected-error{{explicit object member function cannot have '&' qualifier}}
void i(this auto) &&; // expected-error{{explicit object member function cannot have '&&' qualifier}}
@@ -198,9 +198,7 @@ void func(int i) {
void TestMutationInLambda() {
[i = 0](this auto &&){ i++; }();
[i = 0](this auto){ i++; }();
- [i = 0](this const auto&){ i++; }();
- // expected-error@-1 {{cannot assign to a variable captured by copy in a non-mutable lambda}}
- // expected-note@-2 {{in instantiation of}}
+ [i = 0](this const auto&){ i++; }(); // expected-error {{cannot assign to a variable captured by copy in a non-mutable lambda}}
int x;
const auto l1 = [x](this auto&) { x = 42; }; // expected-error {{cannot assign to a variable captured by copy in a non-mutable lambda}}
diff --git a/clang/test/SemaTemplate/class-template-spec.cpp b/clang/test/SemaTemplate/class-template-spec.cpp
index 56b8207bd9a435..faa54c36753831 100644
--- a/clang/test/SemaTemplate/class-template-spec.cpp
+++ b/clang/test/SemaTemplate/class-template-spec.cpp
@@ -18,7 +18,7 @@ int test_specs(A<float, float> *a1, A<float, int> *a2) {
return a1->x + a2->y;
}
-int test_incomplete_specs(A<double, double> *a1,
+int test_incomplete_specs(A<double, double> *a1,
A<double> *a2)
{
(void)a1->x; // expected-error{{member access into incomplete type}}
@@ -39,7 +39,7 @@ template <> struct X<int, int> { int foo(); }; // #1
template <> struct X<float> { int bar(); }; // #2
typedef int int_type;
-void testme(X<int_type> *x1, X<float, int> *x2) {
+void testme(X<int_type> *x1, X<float, int> *x2) {
(void)x1->foo(); // okay: refers to #1
(void)x2->bar(); // okay: refers to #2
}
@@ -53,7 +53,7 @@ struct A<char> {
A<char>::A() { }
// Make sure we can see specializations defined before the primary template.
-namespace N{
+namespace N{
template<typename T> struct A0;
}
@@ -97,7 +97,7 @@ namespace M {
template<> struct ::A<long double>; // expected-error{{must occur at global scope}}
}
-template<> struct N::B<char> {
+template<> struct N::B<char> {
int testf(int x) { return f(x); }
};
@@ -138,9 +138,9 @@ namespace PR18009 {
template <typename T> struct C {
template <int N, int M> struct S;
- template <int N> struct S<N, N ? **(T(*)[N])0 : 0> {}; // expected-error {{depends on a template parameter of the partial specialization}}
+ template <int N> struct S<N, N ? **(T(*)[N])0 : 0> {}; // ok
};
- C<int> c; // expected-note {{in instantiation of}}
+ C<int> c;
template<int A> struct outer {
template<int B, int C> struct inner {};
|
f292ba9
to
6a2d371
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs a release note, likely in the 'potentially breaking changes'. Projects are filled with code that is not ever instantiated, and Clang's aggressive instantiation can be a problem.
Else, LGTM.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this seems to apply to member pointer types, pointer types, arrays and function types but I don't see coverage for the full set of paths in the tests. Can we please add more testing.
Erich also expressed some concern about breaking existing code due to eager instantiation. Can we make sure try to cover any change in behavior in the tests so reviewers can get a better idea of impact.
@erichkeane Release note added |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still OK with this, but @shafik : Please ensure this has the test coverage you asked for.
Yup, working on it :) |
@shafik More tests added |
Ping @shafik |
@erichkeane Since I addressed @shafik's comments, it this good to be merged? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the additional test coverage, LGTM
Yep, go for it! |
This seems to break something in LLVM... investigating |
This is unfortunately one of the cases where if you can't fix it 'quickly', a revert is necessary, as it breaks the build. |
…rs (llvm#90500)" This reverts commit 8019cbb.
Reverted in #92149 |
I've also noticed a stage-2-build issue.
|
Well this took forever to reduce: template<typename T>
struct A;
template<typename T>
bool operator==(const A<T>&, const A<T>&);
template<typename>
void f(int *x)
{
[&](auto *y) { return x == y; };
}
void g()
{
f<int>(nullptr);
} We initially build a |
Yeah, unfortunately that is a limitation of Clang, we still don't correctly implement the instantiation of Lambdas. We do so 'greedily', in a way that the standard doesn't allow. We eventually need to delay ALL instantiation until the actual first full instantiation of the lambda. That said, in this case I'm surprised that we don't skip that diagnostic based on the RHS (in your repro) being dependent. THAT would probably fix this case at least. |
The issue seems to be that |
…y operators (llvm#90500)" (llvm#92283)" This reverts commit 1595988. rdar://131028403 Conflicts: clang/docs/ReleaseNotes.rst
…y operators (llvm#90500)" (llvm#92283)" This reverts commit 1595988. rdar://131028403 Conflicts: clang/docs/ReleaseNotes.rst
Currently, clang postpones all semantic analysis of unary operators with operands of pointer/pointer to member/array/function type until instantiation whenever that type is dependent (e.g.
T*
whereT
is a type template parameter). Consequently, the uninstantiated AST nodes all have the typeASTContext::DependentTy
(which, for the purposes of #90152, is undesirable as that type may be the current instantiation! (e.g.*this
))This patch moves the point at which we perform semantic analysis for such expression to be prior to instantiation.