Skip to content

Commit

Permalink
Improve diagnostics for constant evaluation that fails because a
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
zygoloid committed Jul 9, 2020
1 parent cffc603 commit 00068c4
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 52 deletions.
11 changes: 11 additions & 0 deletions clang/include/clang/Basic/DiagnosticASTKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -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 different 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">;
Expand Down Expand Up @@ -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<
Expand Down
97 changes: 64 additions & 33 deletions clang/lib/AST/ExprConstant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()];
Expand Down Expand Up @@ -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;
}

Expand All @@ -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;
Expand All @@ -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;
}
Expand Down Expand Up @@ -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) {
Expand All @@ -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) {
Expand All @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions clang/test/CXX/expr/expr.const/p2-0x.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand All @@ -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();
Expand Down
10 changes: 6 additions & 4 deletions clang/test/CXX/temp/temp.res/temp.dep/temp.dep.constexpr/p2.cpp
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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 &'}}
}
};
34 changes: 27 additions & 7 deletions clang/test/SemaCXX/constant-expression-cxx11.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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'}}
Expand Down Expand Up @@ -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);
Expand All @@ -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}}
};
Expand Down Expand Up @@ -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);
}
}
2 changes: 1 addition & 1 deletion clang/test/SemaCXX/constant-expression-cxx1y.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(); }

Expand Down
10 changes: 5 additions & 5 deletions clang/test/SemaCXX/cxx1y-variable-templates_in_class.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
Expand All @@ -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.
Expand Down

0 comments on commit 00068c4

Please sign in to comment.