From 08ba9ce1ef7214623d4104e72d817c73644a0884 Mon Sep 17 00:00:00 2001 From: Erich Keane Date: Tue, 11 May 2021 06:40:48 -0700 Subject: [PATCH] Suppress Deferred Diagnostics in discarded statements. It doesn't really make sense to emit language specific diagnostics in a discarded statement, and suppressing these diagnostics results in a programming pattern that many users will feel is quite useful. Basically, this makes sure we only emit errors from the 'true' side of a 'constexpr if'. It does this by making the ExprEvaluatorBase type have an opt-in option as to whether it should visit discarded cases. Differential Revision: https://reviews.llvm.org/D102251 --- clang/include/clang/AST/EvaluatedExprVisitor.h | 19 +++++++++++++- clang/include/clang/AST/Stmt.h | 1 + clang/lib/AST/Stmt.cpp | 10 ++++++- clang/lib/Sema/Sema.cpp | 2 ++ clang/test/SemaCUDA/deferred-diags.cu | 36 ++++++++++++++++++++++---- 5 files changed, 61 insertions(+), 7 deletions(-) diff --git a/clang/include/clang/AST/EvaluatedExprVisitor.h b/clang/include/clang/AST/EvaluatedExprVisitor.h index 2f6c314..2991f28 100644 --- a/clang/include/clang/AST/EvaluatedExprVisitor.h +++ b/clang/include/clang/AST/EvaluatedExprVisitor.h @@ -32,6 +32,9 @@ protected: const ASTContext &Context; public: + // Return whether this visitor should recurse into discarded statements for a + // 'constexpr-if'. + bool shouldVisitDiscardedStmt() const { return true; } #define PTR(CLASS) typename Ptr::type explicit EvaluatedExprVisitorBase(const ASTContext &Context) : Context(Context) { } @@ -83,7 +86,7 @@ public: void VisitCallExpr(PTR(CallExpr) CE) { if (!CE->isUnevaluatedBuiltinCall(Context)) - return static_cast(this)->VisitExpr(CE); + return getDerived().VisitExpr(CE); } void VisitLambdaExpr(PTR(LambdaExpr) LE) { @@ -103,6 +106,20 @@ public: this->Visit(SubStmt); } + void VisitIfStmt(PTR(IfStmt) If) { + if (!getDerived().shouldVisitDiscardedStmt()) { + if (auto SubStmt = If->getNondiscardedCase(Context)) { + if (*SubStmt) + this->Visit(*SubStmt); + return; + } + } + + getDerived().VisitStmt(If); + } + + ImplClass &getDerived() { return *static_cast(this); } + #undef PTR }; diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h index 97cd903..258b17e 100644 --- a/clang/include/clang/AST/Stmt.h +++ b/clang/include/clang/AST/Stmt.h @@ -2080,6 +2080,7 @@ public: /// If this is an 'if constexpr', determine which substatement will be taken. /// Otherwise, or if the condition is value-dependent, returns None. Optional getNondiscardedCase(const ASTContext &Ctx) const; + Optional getNondiscardedCase(const ASTContext &Ctx); bool isObjCAvailabilityCheck() const; diff --git a/clang/lib/AST/Stmt.cpp b/clang/lib/AST/Stmt.cpp index 2ceee61..d30df29 100644 --- a/clang/lib/AST/Stmt.cpp +++ b/clang/lib/AST/Stmt.cpp @@ -989,12 +989,20 @@ bool IfStmt::isObjCAvailabilityCheck() const { return isa(getCond()); } -Optional IfStmt::getNondiscardedCase(const ASTContext &Ctx) const { +Optional IfStmt::getNondiscardedCase(const ASTContext &Ctx) { if (!isConstexpr() || getCond()->isValueDependent()) return None; return !getCond()->EvaluateKnownConstInt(Ctx) ? getElse() : getThen(); } +Optional +IfStmt::getNondiscardedCase(const ASTContext &Ctx) const { + if (Optional Result = + const_cast(this)->getNondiscardedCase(Ctx)) + return *Result; + return None; +} + ForStmt::ForStmt(const ASTContext &C, Stmt *Init, Expr *Cond, VarDecl *condVar, Expr *Inc, Stmt *Body, SourceLocation FL, SourceLocation LP, SourceLocation RP) diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp index b23140b..72e2ee6 100644 --- a/clang/lib/Sema/Sema.cpp +++ b/clang/lib/Sema/Sema.cpp @@ -1569,6 +1569,8 @@ public: DeferredDiagnosticsEmitter(Sema &S) : Inherited(S), ShouldEmitRootNode(false), InOMPDeviceContext(0) {} + bool shouldVisitDiscardedStmt() const { return false; } + void VisitOMPTargetDirective(OMPTargetDirective *Node) { ++InOMPDeviceContext; Inherited::VisitOMPTargetDirective(Node); diff --git a/clang/test/SemaCUDA/deferred-diags.cu b/clang/test/SemaCUDA/deferred-diags.cu index 856a5e0..125ddea 100644 --- a/clang/test/SemaCUDA/deferred-diags.cu +++ b/clang/test/SemaCUDA/deferred-diags.cu @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -fcxx-exceptions -fcuda-is-device -fsyntax-only -verify %s +// RUN: %clang_cc1 -fcxx-exceptions -fcuda-is-device -fsyntax-only -std=c++17 -verify %s #include "Inputs/cuda.h" @@ -8,29 +8,55 @@ inline __host__ __device__ void hasInvalid() { // expected-error@-1 2{{cannot use 'throw' in __host__ __device__ function}} } +inline __host__ __device__ void hasInvalid2() { + throw NULL; + // expected-error@-1 2{{cannot use 'throw' in __host__ __device__ function}} +} + +inline __host__ __device__ void hasInvalidDiscarded() { + // This is only used in the discarded statements below, so this should not diagnose. + throw NULL; +} + static __device__ void use0() { hasInvalid(); // expected-note {{called by 'use0'}} hasInvalid(); // expected-note {{called by 'use0'}} + + if constexpr (true) { + hasInvalid2(); // expected-note {{called by 'use0'}} + } else { + hasInvalidDiscarded(); + } + + if constexpr (false) { + hasInvalidDiscarded(); + } else { + hasInvalid2(); // expected-note {{called by 'use0'}} + } + + if constexpr (false) { + hasInvalidDiscarded(); + } } // To avoid excessive diagnostic messages, deferred diagnostics are only // emitted the first time a function is called. static __device__ void use1() { - use0(); // expected-note 2{{called by 'use1'}} + use0(); // expected-note 4{{called by 'use1'}} use0(); } static __device__ void use2() { - use1(); // expected-note 2{{called by 'use2'}} + use1(); // expected-note 4{{called by 'use2'}} use1(); } static __device__ void use3() { - use2(); // expected-note 2{{called by 'use3'}} + use2(); // expected-note 4{{called by 'use3'}} use2(); } __global__ void use4() { - use3(); // expected-note 2{{called by 'use4'}} + use3(); // expected-note 4{{called by 'use4'}} use3(); } -- 2.7.4