From 7e7f38f853fbf96c6ab2a0e5f9d7747ef8a76ffe Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Tue, 15 Dec 2020 14:18:48 -0800 Subject: [PATCH] DR1413 and part of P1815R2: Minor improvements to Clang's determination of type- and value-dependency. A static data member initialized to a constant inside a class template is no longer considered value-dependent, per DR1413. A const but not constexpr variable of literal type (other than integer or enumeration) is no longer considered value-dependent, per P1815R2. --- clang/include/clang/AST/Decl.h | 3 + clang/lib/AST/ComputeDependence.cpp | 89 +++++++++++++--------- clang/test/CXX/drs/dr14xx.cpp | 23 ++++++ clang/test/CXX/drs/dr21xx.cpp | 17 +++++ clang/test/CXX/drs/dr2xx.cpp | 6 +- .../temp.res/temp.dep/temp.dep.constexpr/p2-0x.cpp | 28 +++++-- clang/test/SemaCXX/typedef-redecl.cpp | 2 +- clang/test/SemaCXX/vector.cpp | 19 ++++- clang/www/cxx_dr_status.html | 2 +- 9 files changed, 139 insertions(+), 50 deletions(-) diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h index 7f6f143..ab24c87 100644 --- a/clang/include/clang/AST/Decl.h +++ b/clang/include/clang/AST/Decl.h @@ -1262,6 +1262,9 @@ public: /// constant expression, according to the relevant language standard. /// This only checks properties of the declaration, and does not check /// whether the initializer is in fact a constant expression. + /// + /// This corresponds to C++20 [expr.const]p3's notion of a + /// "potentially-constant" variable. bool mightBeUsableInConstantExpressions(const ASTContext &C) const; /// Determine whether this variable's value can be used in a diff --git a/clang/lib/AST/ComputeDependence.cpp b/clang/lib/AST/ComputeDependence.cpp index 79e3b3b..4026fdc 100644 --- a/clang/lib/AST/ComputeDependence.cpp +++ b/clang/lib/AST/ComputeDependence.cpp @@ -52,11 +52,12 @@ ExprDependence clang::computeDependence(UnaryOperator *E, // // What this amounts to is: constant-evaluate the operand and check whether it // refers to a templated entity other than a variable with local storage. - if (Ctx.getLangOpts().CPlusPlus11 && E->getOpcode() == UO_AddrOf && + if (Ctx.getLangOpts().CPlusPlus && E->getOpcode() == UO_AddrOf && !(Dep & ExprDependence::Value)) { Expr::EvalResult Result; SmallVector Diag; Result.Diag = &Diag; + // FIXME: This doesn't enforce the C++98 constant expression rules. if (E->getSubExpr()->EvaluateAsConstantExpr(Result, Ctx) && Diag.empty() && Result.Val.isLValue()) { auto *VD = Result.Val.getLValueBase().dyn_cast(); @@ -452,22 +453,21 @@ ExprDependence clang::computeDependence(DeclRefExpr *E, const ASTContext &Ctx) { Deps |= ExprDependence::UnexpandedPack; Deps |= toExprDependence(Type->getDependence()) & ExprDependence::Error; - // (TD) C++ [temp.dep.expr]p3: + // C++ [temp.dep.expr]p3: // An id-expression is type-dependent if it contains: - // - // and - // - // (VD) C++ [temp.dep.constexpr]p2: - // An identifier is value-dependent if it is: - // (TD) - an identifier that was declared with dependent type - // (VD) - a name declared with a dependent type, + // - an identifier associated by name lookup with one or more declarations + // declared with a dependent type + // + // [The "or more" case is not modeled as a DeclRefExpr. There are a bunch + // more bullets here that we handle by treating the declaration as having a + // dependent type if they involve a placeholder type that can't be deduced.] if (Type->isDependentType()) return Deps | ExprDependence::TypeValueInstantiation; else if (Type->isInstantiationDependentType()) Deps |= ExprDependence::Instantiation; - // (TD) - a conversion-function-id that specifies a dependent type + // - a conversion-function-id that specifies a dependent type if (Decl->getDeclName().getNameKind() == DeclarationName::CXXConversionFunctionName) { QualType T = Decl->getDeclName().getCXXNameType(); @@ -478,23 +478,28 @@ ExprDependence clang::computeDependence(DeclRefExpr *E, const ASTContext &Ctx) { Deps |= ExprDependence::Instantiation; } - // (VD) - the name of a non-type template parameter, + // - a template-id that is dependent, + // - a nested-name-specifier or a qualified-id that names a member of an + // unknown specialization + // [These are not modeled as DeclRefExprs.] + + // or if it names a dependent member of the current instantiation that is a + // static data member of type "array of unknown bound of T" for some T + // [handled below]. + + // C++ [temp.dep.constexpr]p2: + // An id-expression is value-dependent if: + + // - it is type-dependent [handled above] + + // - it is the name of a non-type template parameter, if (isa(Decl)) return Deps | ExprDependence::ValueInstantiation; - // (VD) - a constant with integral or enumeration type and is - // initialized with an expression that is value-dependent. - // (VD) - a constant with literal type and is initialized with an - // expression that is value-dependent [C++11]. - // (VD) - FIXME: Missing from the standard: - // - an entity with reference type and is initialized with an - // expression that is value-dependent [C++11] - if (VarDecl *Var = dyn_cast(Decl)) { - if ((Ctx.getLangOpts().CPlusPlus11 - ? Var->getType()->isLiteralType(Ctx) - : Var->getType()->isIntegralOrEnumerationType()) && - (Var->getType().isConstQualified() || - Var->getType()->isReferenceType())) { + // - it names a potentially-constant variable that is initialized with an + // expression that is value-dependent + if (const auto *Var = dyn_cast(Decl)) { + if (Var->mightBeUsableInConstantExpressions(Ctx)) { if (const Expr *Init = Var->getAnyInitializer()) { if (Init->isValueDependent()) Deps |= ExprDependence::ValueInstantiation; @@ -503,25 +508,35 @@ ExprDependence clang::computeDependence(DeclRefExpr *E, const ASTContext &Ctx) { } } - // (VD) - FIXME: Missing from the standard: - // - a member function or a static data member of the current - // instantiation + // - it names a static data member that is a dependent member of the + // current instantiation and is not initialized in a member-declarator, if (Var->isStaticDataMember() && - Var->getDeclContext()->isDependentContext()) { - Deps |= ExprDependence::ValueInstantiation; - TypeSourceInfo *TInfo = Var->getFirstDecl()->getTypeSourceInfo(); - if (TInfo->getType()->isIncompleteArrayType()) - Deps |= ExprDependence::Type; + Var->getDeclContext()->isDependentContext() && + !Var->getFirstDecl()->hasInit()) { + const VarDecl *First = Var->getFirstDecl(); + TypeSourceInfo *TInfo = First->getTypeSourceInfo(); + if (TInfo->getType()->isIncompleteArrayType()) { + Deps |= ExprDependence::TypeValueInstantiation; + } else if (!First->hasInit()) { + Deps |= ExprDependence::ValueInstantiation; + } } return Deps; } - // (VD) - FIXME: Missing from the standard: - // - a member function or a static data member of the current - // instantiation - if (isa(Decl) && Decl->getDeclContext()->isDependentContext()) - Deps |= ExprDependence::ValueInstantiation; + // - it names a static member function that is a dependent member of the + // current instantiation + // + // FIXME: It's unclear that the restriction to static members here has any + // effect: any use of a non-static member function name requires either + // forming a pointer-to-member or providing an object parameter, either of + // which makes the overall expression value-dependent. + if (auto *MD = dyn_cast(Decl)) { + if (MD->isStatic() && Decl->getDeclContext()->isDependentContext()) + Deps |= ExprDependence::ValueInstantiation; + } + return Deps; } diff --git a/clang/test/CXX/drs/dr14xx.cpp b/clang/test/CXX/drs/dr14xx.cpp index c68e6fa..866f2fe 100644 --- a/clang/test/CXX/drs/dr14xx.cpp +++ b/clang/test/CXX/drs/dr14xx.cpp @@ -4,6 +4,29 @@ // RUN: %clang_cc1 -std=c++17 %s -verify -fexceptions -fcxx-exceptions -pedantic-errors // RUN: %clang_cc1 -std=c++2a %s -verify -fexceptions -fcxx-exceptions -pedantic-errors +namespace dr1413 { // dr1413: 12 + template struct Check { + typedef int type; + }; + template struct A : T { + static const int a = 1; + static const int b; + static void c(); + void d(); + + void f() { + Check::type *var1; // expected-error {{undeclared identifier 'var1'}} + Check::type *var2; // ok, variable declaration expected-note 0+{{here}} + Check::type *var3; // expected-error {{undeclared identifier 'var3'}} + Check::type *var4; // expected-error {{undeclared identifier 'var4'}} + // value-dependent because of the implied type-dependent 'this->', not because of 'd' + Check::type *var5; // expected-error {{undeclared identifier 'var5'}} + // value-dependent because of the value-dependent '&' operator, not because of 'A::d' + Check::type *var5; // expected-error {{undeclared identifier 'var5'}} + } + }; +} + namespace dr1423 { // dr1423: 11 #if __cplusplus >= 201103L bool b1 = nullptr; // expected-error {{cannot initialize}} diff --git a/clang/test/CXX/drs/dr21xx.cpp b/clang/test/CXX/drs/dr21xx.cpp index 98593e5..62c5670 100644 --- a/clang/test/CXX/drs/dr21xx.cpp +++ b/clang/test/CXX/drs/dr21xx.cpp @@ -31,6 +31,23 @@ namespace dr2100 { // dr2100: 12 #endif }; int q = A().f() + A().g(); + + // Corresponding constructs where the address is not taken are not + // value-dependent. + template struct Y {}; + template struct B { + static const int n = 1; + int f() { + return Y::declared_later; // expected-error {{no member named 'declared_later'}} + } + int g() { + static const int n = 2; + return Y::declared_later; // expected-error {{no member named 'declared_later'}} + } + }; + template struct Y { + static const int declared_later = 0; + }; } namespace dr2103 { // dr2103: yes diff --git a/clang/test/CXX/drs/dr2xx.cpp b/clang/test/CXX/drs/dr2xx.cpp index c02b7a8..ab70b87 100644 --- a/clang/test/CXX/drs/dr2xx.cpp +++ b/clang/test/CXX/drs/dr2xx.cpp @@ -292,9 +292,9 @@ namespace dr224 { // dr224: no template struct X { typedef T type; }; template class A { static const int i = 5; - X::type w; // FIXME: expected-error {{missing 'typename'}} - X::type x; // FIXME: expected-error {{missing 'typename'}} - X::i, double>::type y; // FIXME: expected-error {{missing 'typename'}} + X::type w; + X::type x; + X::i, double>::type y; X::i, long>::type z; // expected-error {{missing 'typename'}} int f(); }; diff --git a/clang/test/CXX/temp/temp.res/temp.dep/temp.dep.constexpr/p2-0x.cpp b/clang/test/CXX/temp/temp.res/temp.dep/temp.dep.constexpr/p2-0x.cpp index 8f2a599..11dd519 100644 --- a/clang/test/CXX/temp/temp.res/temp.dep/temp.dep.constexpr/p2-0x.cpp +++ b/clang/test/CXX/temp/temp.res/temp.dep/temp.dep.constexpr/p2-0x.cpp @@ -1,7 +1,11 @@ // RUN: %clang_cc1 -std=c++11 -verify %s -// expected-no-diagnostics -template struct S; +template struct S; // expected-note 3{{here}} + +struct LiteralType { + constexpr LiteralType(int n) : n(n) {} + int n; +}; template struct T { T() { @@ -11,18 +15,28 @@ template struct T { S check1; // ok, s is value-dependent // - the name of a non-type template parameter typename S::T check2; // ok, n is value-dependent - // - a constant with literal type and is initialized with an expression - // that is value-dependent. + // - a potentially-constant variable that is initialized with an + // expression that is value-dependent. const int k = n; typename S::T check3a; // ok, u is value-dependent constexpr const int *p = &k; typename S<*p>::T check3b; // ok, p is value-dependent - // (missing from the standard) - // - a reference and is initialized with an expression that is - // value-dependent. const int &i = k; typename S::T check4; // ok, i is value-dependent + + static const int ki = 42; + const int &i2 = ki; + typename S::T check5; // expected-error {{undefined template}} + + constexpr LiteralType x = n; + typename S::T check6; // ok, x is value-dependent + + const LiteralType y = n; + typename S::T check7; // expected-error {{undefined template}} + + constexpr LiteralType z = 42; + typename S::T check8; // expected-error {{undefined template}} } }; diff --git a/clang/test/SemaCXX/typedef-redecl.cpp b/clang/test/SemaCXX/typedef-redecl.cpp index b53bcd2..06883ff 100644 --- a/clang/test/SemaCXX/typedef-redecl.cpp +++ b/clang/test/SemaCXX/typedef-redecl.cpp @@ -86,7 +86,7 @@ namespace PR11630 { void f() { S a; - a.f(); // expected-note{{in instantiation of member function 'PR11630::S::f' requested here}} + a.f(); S2<1> b; b.f(); S2<2> b2; diff --git a/clang/test/SemaCXX/vector.cpp b/clang/test/SemaCXX/vector.cpp index 724ccec..4b2ebc9 100644 --- a/clang/test/SemaCXX/vector.cpp +++ b/clang/test/SemaCXX/vector.cpp @@ -426,6 +426,13 @@ struct ConstantValueNoDiag { } static constexpr double k = 1; }; +template +struct ConstantValueNoDiagDependentValue { + float4 f(float4 x) { + return k * x; + } + static constexpr double k = N; +}; // The following two both diagnose because they cause a truncation. Test both // the dependent type and non-dependent type versions. @@ -437,6 +444,14 @@ struct DiagTrunc { } static constexpr double k = 1340282346638528859811704183484516925443.000000; }; +template +struct DiagTruncDependentValue { + float4 f(float4 x) { + // expected-error@+1{{as implicit conversion would cause truncation}} + return k * x; + } + static constexpr double k = N + 1340282346638528859811704183484516925443.000000; +}; template struct DiagTruncDependentType { float4 f(float4 x) { @@ -467,9 +482,11 @@ void use() { NormalMember().f(theFloat4); #if __cplusplus >= 201103L ConstantValueNoDiag().f(theFloat4); - // expected-note@+1{{in instantiation of member function}} + ConstantValueNoDiagDependentValue().f(theFloat4); DiagTrunc().f(theFloat4); // expected-note@+1{{in instantiation of member function}} + DiagTruncDependentValue().f(theFloat4); + // expected-note@+1{{in instantiation of member function}} DiagTruncDependentType().f(theFloat4); PR45298Consumer().f(theFloat4); #endif // __cplusplus >= 201103L diff --git a/clang/www/cxx_dr_status.html b/clang/www/cxx_dr_status.html index 9f065c3..6e0206a 100755 --- a/clang/www/cxx_dr_status.html +++ b/clang/www/cxx_dr_status.html @@ -8293,7 +8293,7 @@ and POD class 1413 CD3 Missing cases of value-dependency - Unknown + Clang 12 1414 -- 2.7.4