Index: include/clang/Frontend/CompilerInstance.h =================================================================== --- include/clang/Frontend/CompilerInstance.h +++ include/clang/Frontend/CompilerInstance.h @@ -96,6 +96,9 @@ /// The AST context. IntrusiveRefCntPtr Context; + /// An optional sema source that will be attached to sema. + IntrusiveRefCntPtr ExternalSemaSrc; + /// The AST consumer. std::unique_ptr Consumer; @@ -774,6 +777,8 @@ void addDependencyCollector(std::shared_ptr Listener) { DependencyCollectors.push_back(std::move(Listener)); } + + void setExternalSemaSource(IntrusiveRefCntPtr ESS); }; } // end namespace clang Index: include/clang/Sema/TypoCorrection.h =================================================================== --- include/clang/Sema/TypoCorrection.h +++ include/clang/Sema/TypoCorrection.h @@ -230,6 +230,15 @@ bool requiresImport() const { return RequiresImport; } void setRequiresImport(bool Req) { RequiresImport = Req; } + /// Extra diagnostics are printed after the first diagnostic for the typo. + /// This can be used to attach external notes to the diag. + void addExtraDiagnostic(PartialDiagnostic PD) { + ExtraDiagnostics.push_back(std::move(PD)); + } + ArrayRef getExtraDiagnostics() const { + return ExtraDiagnostics; + } + private: bool hasCorrectionDecl() const { return (!isKeyword() && !CorrectionDecls.empty()); @@ -245,6 +254,8 @@ SourceRange CorrectionRange; bool ForceSpecifierReplacement; bool RequiresImport; + + std::vector ExtraDiagnostics; }; /// @brief Base class for callback objects used by Sema::CorrectTypo to check Index: lib/Sema/SemaLookup.cpp =================================================================== --- lib/Sema/SemaLookup.cpp +++ lib/Sema/SemaLookup.cpp @@ -5082,6 +5082,10 @@ if (PrevNote.getDiagID() && ChosenDecl) Diag(ChosenDecl->getLocation(), PrevNote) << CorrectedQuotedStr << (ErrorRecovery ? FixItHint() : FixTypo); + + // Add any extra diagnostics. + for (const PartialDiagnostic &PD : Correction.getExtraDiagnostics()) + Diag(Correction.getCorrectionRange().getBegin(), PD); } TypoExpr *Sema::createDelayedTypo(std::unique_ptr TCC, Index: unittests/Frontend/FrontendActionTest.cpp =================================================================== --- unittests/Frontend/FrontendActionTest.cpp +++ unittests/Frontend/FrontendActionTest.cpp @@ -13,6 +13,7 @@ #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/CompilerInvocation.h" #include "clang/Frontend/FrontendAction.h" +#include "clang/Frontend/FrontendActions.h" #include "clang/Lex/Preprocessor.h" #include "clang/Lex/PreprocessorOptions.h" #include "clang/Sema/Sema.h" @@ -192,4 +193,66 @@ ASSERT_TRUE(TestAction.SeenEnd); } +class TypoExternalSemaSource : public ExternalSemaSource { + CompilerInstance &CI; + +public: + TypoExternalSemaSource(CompilerInstance &CI) : CI(CI) {} + + TypoCorrection CorrectTypo(const DeclarationNameInfo &Typo, int LookupKind, + Scope *S, CXXScopeSpec *SS, + CorrectionCandidateCallback &CCC, + DeclContext *MemberContext, bool EnteringContext, + const ObjCObjectPointerType *OPT) override { + // Generate a fake typo correction with one attached note. + ASTContext &Ctx = CI.getASTContext(); + TypoCorrection TC(DeclarationName(&Ctx.Idents.get("moo"))); + unsigned DiagID = Ctx.getDiagnostics().getCustomDiagID( + DiagnosticsEngine::Note, "This is a note"); + TC.addExtraDiagnostic(PartialDiagnostic(DiagID, Ctx.getDiagAllocator())); + return TC; + } +}; + +struct TypoDiagnosticConsumer : public DiagnosticConsumer { + void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, + const Diagnostic &Info) override { + // Capture errors and notes. There should be one of each. + if (DiagLevel == DiagnosticsEngine::Error) { + assert(Error.empty()); + Info.FormatDiagnostic(Error); + } else { + assert(Note.empty()); + Info.FormatDiagnostic(Note); + } + } + SmallString<32> Error; + SmallString<32> Note; +}; + +TEST(ASTFrontendAction, ExternalSemaSource) { + auto *Invocation = new CompilerInvocation; + Invocation->getLangOpts()->CPlusPlus = true; + Invocation->getPreprocessorOpts().addRemappedFile( + "test.cc", MemoryBuffer::getMemBuffer("void fooo();\n" + "int main() { foo(); }") + .release()); + Invocation->getFrontendOpts().Inputs.push_back( + FrontendInputFile("test.cc", IK_CXX)); + Invocation->getFrontendOpts().ProgramAction = frontend::ParseSyntaxOnly; + Invocation->getTargetOpts().Triple = "i386-unknown-linux-gnu"; + CompilerInstance Compiler; + Compiler.setInvocation(Invocation); + auto *TDC = new TypoDiagnosticConsumer; + Compiler.createDiagnostics(TDC, /*ShouldOwnClient=*/true); + Compiler.setExternalSemaSource(new TypoExternalSemaSource(Compiler)); + + SyntaxOnlyAction TestAction; + ASSERT_TRUE(Compiler.ExecuteAction(TestAction)); + // There should be one error correcting to 'moo' and a note attached to it. + EXPECT_EQ("use of undeclared identifier 'foo'; did you mean 'moo'?", + TDC->Error.str().str()); + EXPECT_EQ("This is a note", TDC->Note.str().str()); +} + } // anonymous namespace