diff --git a/lib/CppInterOp/CppInterOp.cpp b/lib/CppInterOp/CppInterOp.cpp index 893f295ee..0ba950204 100644 --- a/lib/CppInterOp/CppInterOp.cpp +++ b/lib/CppInterOp/CppInterOp.cpp @@ -5206,6 +5206,35 @@ std::string ObjToString(const char* TyRef, void* obj) { return INTEROP_RETURN(getInterp().toString(TyRef, obj)); } +namespace { +/// Traps diagnostics emitted by Sema entry points invoked outside any +/// incremental parse and, when errors were introduced, restores a clean +/// diagnostic state on scope exit — the same cleanup IncrementalParser +/// performs on a real parse failure. Without it, DiagnosticsEngine's sticky +/// ErrorOccurred flag makes the next IncrementalParser::Parse report the +/// stale flag as its own failure (and then soft-reset it), spuriously +/// failing exactly one parse. +class StickyDiagnosticResetRAII { + clang::DiagnosticsEngine* m_Diags; + clang::DiagnosticErrorTrap m_Trap; + +public: + StickyDiagnosticResetRAII(clang::DiagnosticsEngine& Diags) + : m_Diags(&Diags), m_Trap(Diags) {} + StickyDiagnosticResetRAII(const StickyDiagnosticResetRAII&) = delete; + StickyDiagnosticResetRAII& + operator=(const StickyDiagnosticResetRAII&) = delete; + StickyDiagnosticResetRAII(StickyDiagnosticResetRAII&&) = delete; + StickyDiagnosticResetRAII& operator=(StickyDiagnosticResetRAII&&) = delete; + ~StickyDiagnosticResetRAII() { + if (m_Trap.hasErrorOccurred()) { + m_Diags->Reset(/*soft=*/true); + m_Diags->getClient()->clear(); + } + } +}; +} // namespace + static Decl* InstantiateTemplate(TemplateDecl* TemplateD, TemplateArgumentListInfo& TLI, Sema& S, bool instantiate_body) { @@ -5303,6 +5332,10 @@ DeclRef InstantiateTemplate(compat::Interpreter& I, DeclRef tmpl, auto* TmplD = unwrap(tmpl); // We will create a new decl, push a transaction. compat::SynthesizingCodeRAII RAII(&getInterp()); + // A rejected instantiation emits its error diagnostic outside any + // incremental parse; contain it so the sticky error state cannot fail the + // parse that follows. + StickyDiagnosticResetRAII DiagGuard(S.getDiagnostics()); return InstantiateTemplate(TmplD, TemplateArgs, S, instantiate_body); } diff --git a/unittests/CppInterOp/ScopeReflectionTest.cpp b/unittests/CppInterOp/ScopeReflectionTest.cpp index 9ad8c1d20..473e1412d 100644 --- a/unittests/CppInterOp/ScopeReflectionTest.cpp +++ b/unittests/CppInterOp/ScopeReflectionTest.cpp @@ -1245,6 +1245,30 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_InstantiateTemplate) { EXPECT_TRUE(TA4_1.getAsIntegral() == 3); } +TYPED_TEST(CPPINTEROP_TEST_MODE, + ScopeReflection_RejectedInstantiationDoesNotPoisonNextParse) { + std::vector Decls; + std::string code = R"( + struct PoisonTag {}; + template struct PoisonBuf { int t; }; + )"; + + GetAllTopLevelDecls(code, Decls); + + // A type argument for the non-type parameter N: Sema rejects the + // instantiation and emits an error diagnostic outside any incremental + // parse. + Cpp::TypeRef TagTy = Cpp::GetTypeFromScope(Decls[0]); + std::vector args = {TagTy.data}; + EXPECT_FALSE(Cpp::InstantiateTemplate(Decls[1], args)); + + // The sticky DiagnosticsEngine error state must not fail the parses that + // follow (the poison was one-shot: only the first parse after the + // rejection reported failure). + EXPECT_EQ(Cpp::Declare("int rejected_instantiation_probe_1 = 1;"), 0); + EXPECT_EQ(Cpp::Declare("int rejected_instantiation_probe_2 = 2;"), 0); +} + TYPED_TEST(CPPINTEROP_TEST_MODE, ScopeReflection_GetClassTemplateArgs) { std::vector Decls; std::string code = R"(