diff --git a/clang/include/clang/Rewrite/Frontend/FrontendActions.h b/clang/include/clang/Rewrite/Frontend/FrontendActions.h --- a/clang/include/clang/Rewrite/Frontend/FrontendActions.h +++ b/clang/include/clang/Rewrite/Frontend/FrontendActions.h @@ -11,6 +11,7 @@ #include "clang/Frontend/FrontendAction.h" #include "llvm/Support/raw_ostream.h" +#include namespace clang { class FixItRewriter; @@ -26,6 +27,8 @@ StringRef InFile) override; }; +class WrappingFixItAction; + class FixItAction : public ASTFrontendAction { protected: std::unique_ptr Rewriter; @@ -43,6 +46,29 @@ public: FixItAction(); ~FixItAction() override; + friend WrappingFixItAction; +}; + +class WrappingFixItAction : public WrapperFrontendAction { + std::unique_ptr fixItAction; + +public: + WrappingFixItAction(std::unique_ptr WrappedAction) + : WrapperFrontendAction(std::move(WrappedAction)), + fixItAction(std::make_unique()) {} + +protected: + bool BeginSourceFileAction(CompilerInstance &CI) override { + // FIXME: Do we abort if the wrapping action returned failure? + bool Success = fixItAction->BeginSourceFileAction(CI); + Success &= WrapperFrontendAction::BeginSourceFileAction(CI); + return Success; + } + + void EndSourceFileAction() override { + WrapperFrontendAction::EndSourceFileAction(); + fixItAction->EndSourceFileAction(); + } }; /// Emits changes to temporary files and uses them for the original diff --git a/clang/lib/Frontend/Rewrite/FrontendActions.cpp b/clang/lib/Frontend/Rewrite/FrontendActions.cpp --- a/clang/lib/Frontend/Rewrite/FrontendActions.cpp +++ b/clang/lib/Frontend/Rewrite/FrontendActions.cpp @@ -94,7 +94,7 @@ } // end anonymous namespace bool FixItAction::BeginSourceFileAction(CompilerInstance &CI) { - const FrontendOptions &FEOpts = getCompilerInstance().getFrontendOpts(); + const FrontendOptions &FEOpts = CI.getFrontendOpts(); if (!FEOpts.FixItSuffix.empty()) { FixItOpts.reset(new FixItActionSuffixInserter(FEOpts.FixItSuffix, FEOpts.FixWhatYouCan)); diff --git a/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp b/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp --- a/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp +++ b/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp @@ -31,6 +31,7 @@ #include "llvm/Support/BuryPointer.h" #include "llvm/Support/DynamicLibrary.h" #include "llvm/Support/ErrorHandling.h" +#include using namespace clang; using namespace llvm::opt; @@ -182,6 +183,18 @@ CI.setGenModuleActionWrapper(&index::createIndexDataRecordingAction); } + // Forward the FixItAction as a wrapper when building a module, so that + // fix-it hints can be properly applied using the new SourceManager in + // the new CompilerInstance. + // FIXME: Is this the right way to do it? + if (FEOpts.ProgramAction == frontend::FixIt) { + CI.setGenModuleActionWrapper( + [](const FrontendOptions &FEOpts, std::unique_ptr Act) + -> std::unique_ptr { + return std::make_unique(std::move(Act)); + }); + } + // If there are any AST files to merge, create a frontend action // adaptor to perform the merge. if (!FEOpts.ASTMergeFiles.empty()) diff --git a/clang/lib/Lex/PPLexerChange.cpp b/clang/lib/Lex/PPLexerChange.cpp --- a/clang/lib/Lex/PPLexerChange.cpp +++ b/clang/lib/Lex/PPLexerChange.cpp @@ -263,10 +263,11 @@ } void Preprocessor::diagnoseMissingHeaderInUmbrellaDir(const Module &Mod) { - assert(Mod.getUmbrellaHeader() && "Module must use umbrella header"); - SourceLocation StartLoc = - SourceMgr.getLocForStartOfFile(SourceMgr.getMainFileID()); - if (getDiagnostics().isIgnored(diag::warn_uncovered_module_header, StartLoc)) + const auto &UmbrellaHeader = Mod.getUmbrellaHeader(); + assert(UmbrellaHeader.Entry && "Module must use umbrella header"); + const FileID &File = SourceMgr.translateFile(UmbrellaHeader.Entry); + SourceLocation EndLoc = SourceMgr.getLocForEndOfFile(File); + if (getDiagnostics().isIgnored(diag::warn_uncovered_module_header, EndLoc)) return; ModuleMap &ModMap = getHeaderSearchInfo().getModuleMap(); @@ -291,8 +292,26 @@ // Find the relative path that would access this header. SmallString<128> RelativePath; computeRelativePath(FileMgr, Dir, *Header, RelativePath); - Diag(StartLoc, diag::warn_uncovered_module_header) - << Mod.getFullModuleName() << RelativePath; + // FIXME: match CRLF style newlines? + std::string IncludeStmt = + LangOpts.ObjC + ? Twine{"#import \"" + RelativePath + "\"\n"}.str() + : Twine{"#include \"" + RelativePath + "\"\n"}.str(); + unsigned EndOffset = SourceMgr.getDecomposedLoc(EndLoc).second; + // If the umbrella header does not end with a newline, + // insert a newline before inserting FixIt code. + // FIXME: check CRLF style newlines + if (EndOffset > 0) { + SourceLocation LastCharLoc = + SourceMgr.getComposedLoc(File, EndOffset - 1); + if (*SourceMgr.getCharacterData(LastCharLoc) != '\n') + IncludeStmt = '\n' + IncludeStmt; + } + // FIXME: insert the missing include to a better location than EOF. + // For example right after an include block, or in lexical order. + Diag(EndLoc, diag::warn_uncovered_module_header) + << Mod.getFullModuleName() << RelativePath + << FixItHint::CreateInsertion(EndLoc, IncludeStmt); } } } diff --git a/clang/test/Modules/Inputs/incomplete-umbrella/normal-module/A1.h b/clang/test/Modules/Inputs/incomplete-umbrella/normal-module/A1.h new file mode 100644 --- /dev/null +++ b/clang/test/Modules/Inputs/incomplete-umbrella/normal-module/A1.h @@ -0,0 +1 @@ +// A1.h diff --git a/clang/test/Modules/Inputs/incomplete-umbrella/normal-module/A2.h b/clang/test/Modules/Inputs/incomplete-umbrella/normal-module/A2.h new file mode 100644 --- /dev/null +++ b/clang/test/Modules/Inputs/incomplete-umbrella/normal-module/A2.h @@ -0,0 +1 @@ +// A2.h diff --git a/clang/test/Modules/Inputs/incomplete-umbrella/normal-module/Umbrella.h b/clang/test/Modules/Inputs/incomplete-umbrella/normal-module/Umbrella.h new file mode 100644 --- /dev/null +++ b/clang/test/Modules/Inputs/incomplete-umbrella/normal-module/Umbrella.h @@ -0,0 +1,3 @@ +// Umbrella.h +// CHECK: #import "A1.h" +// CHECK: #import "A2.h" diff --git a/clang/test/Modules/Inputs/incomplete-umbrella/normal-module/module.modulemap b/clang/test/Modules/Inputs/incomplete-umbrella/normal-module/module.modulemap new file mode 100644 --- /dev/null +++ b/clang/test/Modules/Inputs/incomplete-umbrella/normal-module/module.modulemap @@ -0,0 +1,4 @@ +module A { + umbrella header "Umbrella.h" + export * +} diff --git a/clang/test/Modules/incomplete-umbrella-fixit.m b/clang/test/Modules/incomplete-umbrella-fixit.m new file mode 100644 --- /dev/null +++ b/clang/test/Modules/incomplete-umbrella-fixit.m @@ -0,0 +1,10 @@ +// RUN: rm -rf %t +// RUN: cp -R %S/Inputs/incomplete-umbrella/normal-module %t +// RUN: %clang_cc1 -fmodules -fmodule-map-file=%t/module.modulemap -fmodules-cache-path=%t -Wno-everything -Wincomplete-umbrella -fixit %s && FileCheck %t/Umbrella.h --input-file %t/Umbrella.h --match-full-lines +// RUN: %clang_cc1 -fmodules -fmodule-map-file=%t/module.modulemap -fmodules-cache-path=%t -Wno-everything -Wincomplete-umbrella -Werror %s + +@import A; + +int foo() { + return 0; +} diff --git a/clang/test/Modules/incomplete-umbrella.m b/clang/test/Modules/incomplete-umbrella.m --- a/clang/test/Modules/incomplete-umbrella.m +++ b/clang/test/Modules/incomplete-umbrella.m @@ -6,8 +6,12 @@ #import @import Foo.Private; -// CHECK: warning: umbrella header for module 'Foo' does not include header 'Bar.h' -// CHECK: warning: umbrella header for module 'Foo.Private' does not include header 'Baz.h' +// CHECK: While building module 'Foo' imported from {{.*[/\]}}incomplete-umbrella.m:4: +// CHECK-NEXT: In file included from :1: +// CHECK-NEXT: {{.*Foo[.]framework[/\]Headers[/\]}}FooPublic.h:2:1: warning: umbrella header for module 'Foo' does not include header 'Bar.h' +// CHECK: While building module 'Foo' imported from {{.*[/\]}}incomplete-umbrella.m:4: +// CHECK-NEXT: In file included from :2: +// CHECK-NEXT: {{.*Foo[.]framework[/\]PrivateHeaders[/\]}}Foo.h:2:1: warning: umbrella header for module 'Foo.Private' does not include header 'Baz.h' int foo() { int a = BAR_PUBLIC; int b = BAZ_PRIVATE;