Skip to content

Commit e0cdafe

Browse files
committed
[AST] Better recovery on an expression refers to an invalid decl.
Prior to the patch, we didn't build a DeclRefExpr if the Decl being referred to is invalid, because many clang downstream AST consumers assume it, violating it will cause many diagnostic regressions. With this patch, we build a DeclRefExpr enven for an invalid decl (when the AcceptInvalidDecl is true), and wrap it with a dependent-type RecoveryExpr (to prevent follow-up semantic analysis, and diagnostic regressions). This is a revised version of https://reviews.llvm.org/D76831 Reviewed By: sammccall Differential Revision: https://reviews.llvm.org/D121599
1 parent 525a400 commit e0cdafe

File tree

6 files changed

+38
-27
lines changed

6 files changed

+38
-27
lines changed

clang/lib/Sema/SemaDecl.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1275,7 +1275,7 @@ ExprResult Sema::ActOnNameClassifiedAsNonType(Scope *S, const CXXScopeSpec &SS,
12751275
Result.resolveKind();
12761276

12771277
bool ADL = UseArgumentDependentLookup(SS, Result, NextToken.is(tok::l_paren));
1278-
return BuildDeclarationNameExpr(SS, Result, ADL);
1278+
return BuildDeclarationNameExpr(SS, Result, ADL, /*AcceptInvalidDecl=*/true);
12791279
}
12801280

12811281
ExprResult Sema::ActOnNameClassifiedAsOverloadSet(Scope *S, Expr *E) {

clang/lib/Sema/SemaExpr.cpp

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3183,8 +3183,9 @@ bool Sema::UseArgumentDependentLookup(const CXXScopeSpec &SS,
31833183
/// as an expression. This is only actually called for lookups that
31843184
/// were not overloaded, and it doesn't promise that the declaration
31853185
/// will in fact be used.
3186-
static bool CheckDeclInExpr(Sema &S, SourceLocation Loc, NamedDecl *D) {
3187-
if (D->isInvalidDecl())
3186+
static bool CheckDeclInExpr(Sema &S, SourceLocation Loc, NamedDecl *D,
3187+
bool AcceptInvalid) {
3188+
if (D->isInvalidDecl() && !AcceptInvalid)
31883189
return true;
31893190

31903191
if (isa<TypedefNameDecl>(D)) {
@@ -3230,7 +3231,8 @@ ExprResult Sema::BuildDeclarationNameExpr(const CXXScopeSpec &SS,
32303231
// result, because in the overloaded case the results can only be
32313232
// functions and function templates.
32323233
if (R.isSingleResult() && !ShouldLookupResultBeMultiVersionOverload(R) &&
3233-
CheckDeclInExpr(*this, R.getNameLoc(), R.getFoundDecl()))
3234+
CheckDeclInExpr(*this, R.getNameLoc(), R.getFoundDecl(),
3235+
AcceptInvalidDecl))
32343236
return ExprError();
32353237

32363238
// Otherwise, just build an unresolved lookup expression. Suppress
@@ -3262,7 +3264,7 @@ ExprResult Sema::BuildDeclarationNameExpr(
32623264
"Cannot refer unambiguously to a function template");
32633265

32643266
SourceLocation Loc = NameInfo.getLoc();
3265-
if (CheckDeclInExpr(*this, Loc, D)) {
3267+
if (CheckDeclInExpr(*this, Loc, D, AcceptInvalidDecl)) {
32663268
// Recovery from invalid cases (e.g. D is an invalid Decl).
32673269
// We use the dependent type for the RecoveryExpr to prevent bogus follow-up
32683270
// diagnostics, as invalid decls use int as a fallback type.
@@ -3494,9 +3496,16 @@ ExprResult Sema::BuildDeclarationNameExpr(
34943496
break;
34953497
}
34963498

3497-
return BuildDeclRefExpr(VD, type, valueKind, NameInfo, &SS, FoundD,
3498-
/*FIXME: TemplateKWLoc*/ SourceLocation(),
3499-
TemplateArgs);
3499+
auto *E =
3500+
BuildDeclRefExpr(VD, type, valueKind, NameInfo, &SS, FoundD,
3501+
/*FIXME: TemplateKWLoc*/ SourceLocation(), TemplateArgs);
3502+
// Clang AST consumers assume a DeclRefExpr refers to a valid decl. We
3503+
// wrap a DeclRefExpr referring to an invalid decl with a dependent-type
3504+
// RecoveryExpr to avoid follow-up semantic analysis (thus prevent bogus
3505+
// diagnostics).
3506+
if (VD->isInvalidDecl() && E)
3507+
return CreateRecoveryExpr(E->getBeginLoc(), E->getEndLoc(), {E});
3508+
return E;
35003509
}
35013510

35023511
static void ConvertUTF8ToWideString(unsigned CharByteWidth, StringRef Source,

clang/test/AST/ast-dump-recovery.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,8 +406,17 @@ void RecoveryExprForInvalidDecls(Unknown InvalidDecl) {
406406
InvalidDecl + 1;
407407
// CHECK: BinaryOperator {{.*}}
408408
// CHECK-NEXT: |-RecoveryExpr {{.*}} '<dependent type>'
409+
// CHECK-NEXT: | | `-DeclRefExpr {{.*}} 'InvalidDecl' 'int'
409410
// CHECK-NEXT: `-IntegerLiteral {{.*}} 'int' 1
410411
InvalidDecl();
411412
// CHECK: CallExpr {{.*}}
412413
// CHECK-NEXT: `-RecoveryExpr {{.*}} '<dependent type>'
413414
}
415+
416+
void RecoverToAnInvalidDecl() {
417+
Unknown* foo; // invalid decl
418+
goo; // the typo was correct to the invalid foo.
419+
// Verify that RecoveryExpr has an inner DeclRefExpr.
420+
// CHECK: RecoveryExpr {{.*}} '<dependent type>' contains-errors lvalue
421+
// CHECK-NEXT: `-DeclRefExpr {{.*}} 'foo' 'int *'
422+
}

clang/test/SemaTemplate/constraints.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,11 @@ namespace PR45589 {
1515

1616
template<bool B, typename T> constexpr int test = 0;
1717
template<bool B, typename T> requires C<T> constexpr int test<B, T> = 1;
18-
template<bool B, typename T> requires (B && C<T>) || (X<T>::value && C<T>) constexpr int test<B, T> = 2; // expected-error {{non-constant expression}} expected-note {{subexpression}} expected-note {{instantiation of}} expected-note {{while substituting}}
18+
template<bool B, typename T> requires (B && C<T>) || (X<T>::value && C<T>) constexpr int test<B, T> = 2; // expected-note {{instantiation of}} expected-note {{while substituting}}
1919
static_assert(test<true, False> == 2);
2020
static_assert(test<true, True> == 2);
2121
static_assert(test<true, char> == 2); // satisfaction of second term of || not considered
2222
static_assert(test<false, False> == 1);
2323
static_assert(test<false, True> == 2); // constraints are partially ordered
24-
// FIXME: These diagnostics are excessive.
25-
static_assert(test<false, char> == 1); // expected-note 2{{while}} expected-note 2{{during}}
24+
static_assert(test<false, char> == 1); // expected-note {{while}} expected-note {{during}}
2625
}

clang/test/SemaTemplate/cxx2a-constraint-exprs.cpp

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,28 +26,22 @@ namespace unevaluated {
2626

2727
namespace constant_evaluated {
2828
template<typename T> requires f<int[0]> struct S {};
29-
// expected-note@-1{{in instantiation of}} expected-note@-1{{while substituting}} \
30-
expected-error@-1{{substitution into constraint expression resulted in a non-constant expression}} \
31-
expected-note@-1{{subexpression not valid}}
29+
// expected-note@-1{{in instantiation of}} expected-note@-1{{while substituting}}
3230
using s = S<int>;
33-
// expected-note@-1 2{{while checking}}
31+
// expected-note@-1 {{while checking}}
3432
template<typename T> void foo() requires f<int[1]> { };
3533
// expected-note@-1{{in instantiation}} expected-note@-1{{while substituting}} \
36-
expected-note@-1{{candidate template ignored}} expected-note@-1{{subexpression not valid}} \
37-
expected-error@-1{{substitution into constraint expression resulted in a non-constant expression}}
34+
expected-note@-1{{candidate template ignored}}
3835
int a = (foo<int>(), 0);
39-
// expected-note@-1 2{{while checking}} expected-error@-1{{no matching function}} \
40-
expected-note@-1 2{{in instantiation}}
36+
// expected-note@-1 {{while checking}} expected-error@-1{{no matching function}} \
37+
expected-note@-1 {{in instantiation}}
4138
template<typename T> void bar() requires requires { requires f<int[2]>; } { };
42-
// expected-note@-1{{in instantiation}} expected-note@-1{{subexpression not valid}} \
39+
// expected-note@-1{{in instantiation}} \
4340
expected-note@-1{{while substituting}} \
44-
expected-error@-1{{substitution into constraint expression resulted in a non-constant expression}} \
45-
expected-note@-1 2{{while checking the satisfaction of nested requirement}}
41+
expected-note@-1 {{while checking the satisfaction of nested requirement}}
4642
int b = (bar<int>(), 0);
4743
template<typename T> struct M { static void foo() requires f<int[3]> { }; };
48-
// expected-note@-1{{in instantiation}} expected-note@-1{{subexpression not valid}} \
49-
expected-note@-1{{while substituting}} \
50-
expected-error@-1{{substitution into constraint expression resulted in a non-constant expression}}
44+
// expected-note@-1{{in instantiation}} expected-note@-1{{while substituting}}
5145
int c = (M<int>::foo(), 0);
52-
// expected-note@-1 2{{while checking}}
46+
// expected-note@-1 {{while checking}}
5347
}

clang/test/SemaTemplate/instantiate-var-template.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ namespace InstantiationDependent {
2828
template<typename T> constexpr T b = a<sizeof(sizeof(f(T())))>; // expected-error {{invalid application of 'sizeof' to an incomplete type 'void'}}
2929

3030
static_assert(b<int> == 1, "");
31-
static_assert(b<char> == 1, ""); // expected-note {{in instantiation of}} expected-error {{not an integral constant}}
31+
static_assert(b<char> == 1, ""); // expected-note {{in instantiation of}}
3232

3333
template<typename T> void f() {
3434
static_assert(a<sizeof(sizeof(f(T())))> == 0, ""); // expected-error {{static assertion failed due to requirement 'a<sizeof (sizeof (f(type-parameter-0-0())))> == 0'}} \

0 commit comments

Comments
 (0)