Author: Richard Smith Date: 2020-07-08T18:14:23-07:00 New Revision: 00068c452a599c328986e8afcbb3311331d09d26
URL: https://github.com/llvm/llvm-project/commit/00068c452a599c328986e8afcbb3311331d09d26 DIFF: https://github.com/llvm/llvm-project/commit/00068c452a599c328986e8afcbb3311331d09d26.diff LOG: Improve diagnostics for constant evaluation that fails because a variable's initializer is not known. The hope is that a better diagnostic for this case will reduce the rate at which duplicates of non-bug PR41093 are reported. Added: Modified: clang/include/clang/Basic/DiagnosticASTKinds.td clang/lib/AST/ExprConstant.cpp clang/test/CXX/expr/expr.const/p2-0x.cpp clang/test/CXX/temp/temp.res/temp.dep/temp.dep.constexpr/p2.cpp clang/test/SemaCXX/constant-expression-cxx11.cpp clang/test/SemaCXX/constant-expression-cxx1y.cpp clang/test/SemaCXX/cxx1y-variable-templates_in_class.cpp Removed: ################################################################################ diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td index 905e3158cf40..10bedaaf7aba 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -101,8 +101,16 @@ def note_constexpr_null_subobject : Note< "access array element of|perform pointer arithmetic on|" "access real component of|" "access imaginary component of}0 null pointer">; +def note_constexpr_function_param_value_unknown : Note< + "function parameter %0 with unknown value cannot be used in a constant " + "expression">; +def note_constexpr_var_init_unknown : Note< + "initializer of %0 is unknown">; def note_constexpr_var_init_non_constant : Note< "initializer of %0 is not a constant expression">; +def note_constexpr_var_init_weak : Note< + "initializer of weak variable %0 is not considered constant because " + "it may be diff erent at runtime">; def note_constexpr_typeid_polymorphic : Note< "typeid applied to expression of polymorphic type %0 is " "not allowed in a constant expression in C++ standards before C++20">; @@ -159,6 +167,9 @@ def note_constexpr_access_mutable : Note< "mutable member %1 is not allowed in a constant expression">; def note_constexpr_ltor_non_const_int : Note< "read of non-const variable %0 is not allowed in a constant expression">; +def note_constexpr_ltor_non_integral : Note< + "read of variable %0 of non-integral, non-enumeration type %1 " + "is not allowed in a constant expression">; def note_constexpr_ltor_non_constexpr : Note< "read of non-constexpr variable %0 is not allowed in a constant expression">; def note_constexpr_ltor_incomplete_type : Note< diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 9eba40c44ddc..d6dbfb14e60b 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -3025,7 +3025,7 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E, if (Info.checkingPotentialConstantExpression()) return false; if (!Frame || !Frame->Arguments) { - Info.FFDiag(E, diag::note_invalid_subexpr_in_const_expr); + Info.FFDiag(E, diag::note_constexpr_function_param_value_unknown) << VD; return false; } Result = &Frame->Arguments[PVD->getFunctionScopeIndex()]; @@ -3056,12 +3056,34 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E, } // Dig out the initializer, and use the declaration which it's attached to. + // FIXME: We should eventually check whether the variable has a reachable + // initializing declaration. const Expr *Init = VD->getAnyInitializer(VD); - if (!Init || Init->isValueDependent()) { - // If we're checking a potential constant expression, the variable could be - // initialized later. - if (!Info.checkingPotentialConstantExpression()) - Info.FFDiag(E, diag::note_invalid_subexpr_in_const_expr); + if (!Init) { + // Don't diagnose during potential constant expression checking; an + // initializer might be added later. + if (!Info.checkingPotentialConstantExpression()) { + Info.FFDiag(E, diag::note_constexpr_var_init_unknown, 1) + << VD; + Info.Note(VD->getLocation(), diag::note_declared_at); + } + return false; + } + + if (Init->isValueDependent()) { + // The DeclRefExpr is not value-dependent, but the variable it refers to + // has a value-dependent initializer. This should only happen in + // constant-folding cases, where the variable is not actually of a suitable + // type for use in a constant expression (otherwise the DeclRefExpr would + // have been value-dependent too), so diagnose that. + assert(!VD->mightBeUsableInConstantExpressions(Info.Ctx)); + if (!Info.checkingPotentialConstantExpression()) { + Info.FFDiag(E, Info.getLangOpts().CPlusPlus11 + ? diag::note_constexpr_ltor_non_constexpr + : diag::note_constexpr_ltor_non_integral, 1) + << VD << VD->getType(); + Info.Note(VD->getLocation(), diag::note_declared_at); + } return false; } @@ -3072,13 +3094,6 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E, return true; } - // Never evaluate the initializer of a weak variable. We can't be sure that - // this is the definition which will be used. - if (VD->isWeak()) { - Info.FFDiag(E, diag::note_invalid_subexpr_in_const_expr); - return false; - } - // Check that we can fold the initializer. In C++, we will have already done // this in the cases where it matters for conformance. SmallVector<PartialDiagnosticAt, 8> Notes; @@ -3088,13 +3103,24 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E, Info.Note(VD->getLocation(), diag::note_declared_at); Info.addNotes(Notes); return false; - } else if (!VD->checkInitIsICE()) { + } + + // Check that the variable is actually usable in constant expressions. + if (!VD->checkInitIsICE()) { Info.CCEDiag(E, diag::note_constexpr_var_init_non_constant, Notes.size() + 1) << VD; Info.Note(VD->getLocation(), diag::note_declared_at); Info.addNotes(Notes); } + // Never use the initializer of a weak variable, not even for constant + // folding. We can't be sure that this is the definition that will be used. + if (VD->isWeak()) { + Info.FFDiag(E, diag::note_constexpr_var_init_weak) << VD; + Info.Note(VD->getLocation(), diag::note_declared_at); + return false; + } + Result = VD->getEvaluatedValue(); return true; } @@ -3797,6 +3823,11 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, return CompleteObject(); } + // In OpenCL if a variable is in constant address space it is a const value. + bool IsConstant = BaseType.isConstQualified() || + (Info.getLangOpts().OpenCL && + BaseType.getAddressSpace() == LangAS::opencl_constant); + // Unless we're looking at a local variable or argument in a constexpr call, // the variable we're reading must be const. if (!Frame) { @@ -3814,9 +3845,7 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, } else if (BaseType->isIntegralOrEnumerationType()) { // In OpenCL if a variable is in constant address space it is a const // value. - if (!(BaseType.isConstQualified() || - (Info.getLangOpts().OpenCL && - BaseType.getAddressSpace() == LangAS::opencl_constant))) { + if (!IsConstant) { if (!IsAccess) return CompleteObject(LVal.getLValueBase(), nullptr, BaseType); if (Info.getLangOpts().CPlusPlus) { @@ -3829,27 +3858,29 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, } } else if (!IsAccess) { return CompleteObject(LVal.getLValueBase(), nullptr, BaseType); - } else if (BaseType->isFloatingType() && BaseType.isConstQualified()) { - // We support folding of const floating-point types, in order to make - // static const data members of such types (supported as an extension) - // more useful. - if (Info.getLangOpts().CPlusPlus11) { - Info.CCEDiag(E, diag::note_constexpr_ltor_non_constexpr, 1) << VD; + } else if (IsConstant && Info.checkingPotentialConstantExpression() && + BaseType->isLiteralType(Info.Ctx) && !VD->hasDefinition()) { + // This variable might end up being constexpr. Don't diagnose it yet. + } else if (IsConstant) { + // Keep evaluating to see what we can do. In particular, we support + // folding of const floating-point types, in order to make static const + // data members of such types (supported as an extension) more useful. + if (Info.getLangOpts().CPlusPlus) { + Info.CCEDiag(E, Info.getLangOpts().CPlusPlus11 + ? diag::note_constexpr_ltor_non_constexpr + : diag::note_constexpr_ltor_non_integral, 1) + << VD << BaseType; Info.Note(VD->getLocation(), diag::note_declared_at); } else { Info.CCEDiag(E); } - } else if (BaseType.isConstQualified() && VD->hasDefinition(Info.Ctx)) { - Info.CCEDiag(E, diag::note_constexpr_ltor_non_constexpr) << VD; - // Keep evaluating to see what we can do. } else { - // FIXME: Allow folding of values of any literal type in all languages. - if (Info.checkingPotentialConstantExpression() && - VD->getType().isConstQualified() && !VD->hasDefinition(Info.Ctx)) { - // The definition of this variable could be constexpr. We can't - // access it right now, but may be able to in future. - } else if (Info.getLangOpts().CPlusPlus11) { - Info.FFDiag(E, diag::note_constexpr_ltor_non_constexpr, 1) << VD; + // Never allow reading a non-const value. + if (Info.getLangOpts().CPlusPlus) { + Info.FFDiag(E, Info.getLangOpts().CPlusPlus11 + ? diag::note_constexpr_ltor_non_constexpr + : diag::note_constexpr_ltor_non_integral, 1) + << VD << BaseType; Info.Note(VD->getLocation(), diag::note_declared_at); } else { Info.FFDiag(E); diff --git a/clang/test/CXX/expr/expr.const/p2-0x.cpp b/clang/test/CXX/expr/expr.const/p2-0x.cpp index c418767f8d12..b9235eeeb172 100644 --- a/clang/test/CXX/expr/expr.const/p2-0x.cpp +++ b/clang/test/CXX/expr/expr.const/p2-0x.cpp @@ -376,7 +376,7 @@ namespace References { int &d = c; constexpr int e = 42; int &f = const_cast<int&>(e); - extern int &g; + extern int &g; // expected-note {{here}} constexpr int &h(); // expected-note {{here}} int &i = h(); // expected-note {{here}} constexpr int &j() { return b; } @@ -390,7 +390,7 @@ namespace References { int D2 : &d - &c + 1; int E : e / 2; int F : f - 11; - int G : g; // expected-error {{constant expression}} + int G : g; // expected-error {{constant expression}} expected-note {{initializer of 'g' is unknown}} int H : h(); // expected-error {{constant expression}} expected-note {{undefined function 'h'}} int I : i; // expected-error {{constant expression}} expected-note {{initializer of 'i' is not a constant expression}} int J : j(); diff --git a/clang/test/CXX/temp/temp.res/temp.dep/temp.dep.constexpr/p2.cpp b/clang/test/CXX/temp/temp.res/temp.dep/temp.dep.constexpr/p2.cpp index 6c0df1aff231..ecb82372bcb4 100644 --- a/clang/test/CXX/temp/temp.res/temp.dep/temp.dep.constexpr/p2.cpp +++ b/clang/test/CXX/temp/temp.res/temp.dep/temp.dep.constexpr/p2.cpp @@ -1,4 +1,6 @@ -// RUN: %clang_cc1 -std=c++98 -verify %s +// RUN: %clang_cc1 -std=c++98 -verify=cxx98 %s +// RUN: %clang_cc1 -std=c++11 -verify=cxx11 %s +// cxx11-no-diagnostics template<int n> struct S; @@ -13,9 +15,9 @@ template<int n> struct T { // - a constant with literal type and is initialized with an expression // that is value-dependent. const int k = n; - typename S<k>::T check3; // ok, u is value-dependent + typename S<k>::T check3; // ok, k is value-dependent - const int &i = k; - typename S<i>::T check4; // expected-error {{not an integral constant expression}} + const int &i = k; // cxx98-note {{declared here}} + typename S<i>::T check4; // cxx98-error {{not an integral constant expression}} cxx98-note {{read of variable 'i' of non-integral, non-enumeration type 'const int &'}} } }; diff --git a/clang/test/SemaCXX/constant-expression-cxx11.cpp b/clang/test/SemaCXX/constant-expression-cxx11.cpp index dd6d7ccba19f..78e9fef96c8d 100644 --- a/clang/test/SemaCXX/constant-expression-cxx11.cpp +++ b/clang/test/SemaCXX/constant-expression-cxx11.cpp @@ -682,12 +682,12 @@ template<bool B, typename T> struct S : T { } }; -extern const int n; +extern const int n; // expected-note {{declared here}} template<typename T> void f() { // This is ill-formed, because a hypothetical instantiation at the point of // template definition would be ill-formed due to a construct that does not // depend on a template parameter. - constexpr int k = n; // expected-error {{must be initialized by a constant expression}} + constexpr int k = n; // expected-error {{must be initialized by a constant expression}} expected-note {{initializer of 'n' is unknown}} } // It doesn't matter that the instantiation could later become valid: constexpr int n = 4; @@ -1258,7 +1258,7 @@ constexpr int m1b = const_cast<const int&>(n1); // expected-error {{constant exp constexpr int m2b = const_cast<const int&>(n2); // expected-error {{constant expression}} expected-note {{read of volatile object 'n2'}} struct T { int n; }; -const T t = { 42 }; +const T t = { 42 }; // expected-note {{declared here}} constexpr int f(volatile int &&r) { return r; // expected-note {{read of volatile-qualified type 'volatile int'}} @@ -1372,7 +1372,7 @@ namespace InstantiateCaseStmt { namespace ConvertedConstantExpr { extern int &m; - extern int &n; + extern int &n; // expected-note 2{{declared here}} constexpr int k = 4; int &m = const_cast<int&>(k); @@ -1381,9 +1381,9 @@ namespace ConvertedConstantExpr { // useless note and instead just point to the non-constant subexpression. enum class E { em = m, - en = n, // expected-error {{not a constant expression}} - eo = (m + - n // expected-error {{not a constant expression}} + en = n, // expected-error {{not a constant expression}} expected-note {{initializer of 'n' is unknown}} + eo = (m + // expected-error {{not a constant expression}} + n // expected-note {{initializer of 'n' is unknown}} ), eq = reinterpret_cast<long>((int*)0) // expected-error {{not a constant expression}} expected-note {{reinterpret_cast}} }; @@ -2302,3 +2302,23 @@ namespace PR41854 { f &d = reinterpret_cast<f&>(a); unsigned b = d.c; } + +namespace array_size { + template<int N> struct array { + static constexpr int size() { return N; } + }; + template<typename T> void f1(T t) { + constexpr int k = t.size(); + } + template<typename T> void f2(const T &t) { + constexpr int k = t.size(); // expected-error {{constant}} expected-note {{function parameter 't' with unknown value cannot be used in a constant expression}} + } + template<typename T> void f3(const T &t) { + constexpr int k = T::size(); + } + void g(array<3> a) { + f1(a); + f2(a); // expected-note {{instantiation of}} + f3(a); + } +} diff --git a/clang/test/SemaCXX/constant-expression-cxx1y.cpp b/clang/test/SemaCXX/constant-expression-cxx1y.cpp index 5a414799f755..8bc4f88a63a9 100644 --- a/clang/test/SemaCXX/constant-expression-cxx1y.cpp +++ b/clang/test/SemaCXX/constant-expression-cxx1y.cpp @@ -1009,7 +1009,7 @@ constexpr int sum(const char (&Arr)[N]) { // As an extension, we support evaluating some things that are `const` as though // they were `constexpr` when folding, but it should not be allowed in normal // constexpr evaluation. -const char Cs[] = {'a', 'b'}; +const char Cs[] = {'a', 'b'}; // expected-note 2{{declared here}} void foo() __attribute__((enable_if(sum(Cs) == 'a' + 'b', ""))); void run() { foo(); } diff --git a/clang/test/SemaCXX/cxx1y-variable-templates_in_class.cpp b/clang/test/SemaCXX/cxx1y-variable-templates_in_class.cpp index a16a5b54d8e0..fc49ec88d553 100644 --- a/clang/test/SemaCXX/cxx1y-variable-templates_in_class.cpp +++ b/clang/test/SemaCXX/cxx1y-variable-templates_in_class.cpp @@ -5,7 +5,7 @@ #define CONST const #ifdef PRECXX11 -#define static_assert(expr, msg) typedef int static_assert[(expr) ? 1 : -1]; +#define static_assert _Static_assert #endif class A { @@ -237,7 +237,7 @@ namespace in_class_template { namespace definition_after_outer_instantiation { template<typename A> struct S { template<typename B> static const int V1; - template<typename B> static const int V2; + template<typename B> static const int V2; // expected-note 3{{here}} }; template struct S<int>; template<typename A> template<typename B> const int S<A>::V1 = 123; @@ -250,11 +250,11 @@ namespace in_class_template { // is instantiated. This is kind of implied by [temp.class.spec.mfunc]/2, // and matches our behavior for member class templates, but it's not clear // that this is intentional. See PR17294 and core-24030. - static_assert(S<int>::V2<int*> == 456, ""); // FIXME expected-error {{}} - static_assert(S<int>::V2<int&> == 789, ""); // expected-error {{}} + static_assert(S<int>::V2<int*> == 456, ""); // FIXME expected-error {{}} expected-note {{initializer of 'V2<int *>' is unknown}} + static_assert(S<int>::V2<int&> == 789, ""); // expected-error {{}} expected-note {{initializer of 'V2<int &>' is unknown}} template<typename A> template<typename B> const int S<A>::V2<B&> = 789; - static_assert(S<int>::V2<int&> == 789, ""); // FIXME expected-error {{}} + static_assert(S<int>::V2<int&> == 789, ""); // FIXME expected-error {{}} expected-note {{initializer of 'V2<int &>' is unknown}} // All is OK if the partial specialization is declared before the implicit // instantiation of the class template specialization. _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits