forked from OSchip/llvm-project
[clang][pp] adds '#pragma include_instead'
`#pragma clang include_instead(<header>)` is a pragma that can be used by system headers (and only system headers) to indicate to a tool that the file containing said pragma is an implementation-detail header and should not be directly included by user code. The library alternative is very messy code that can be seen in the first diff of D106124, and we'd rather avoid that with something more universal. This patch takes the first step by warning a user when they include a detail header in their code, and suggests alternative headers that the user should include instead. Future work will involve adding a fixit to automate the process, as well as cleaning up modules diagnostics to not suggest said detail headers. Other tools, such as clangd can also take advantage of this pragma to add the correct user headers. Differential Revision: https://reviews.llvm.org/D106394
This commit is contained in:
parent
f921bf6049
commit
e8a64e5491
|
@ -300,6 +300,12 @@ def pp_pragma_once_in_main_file : Warning<"#pragma once in main file">,
|
|||
def pp_pragma_sysheader_in_main_file : Warning<
|
||||
"#pragma system_header ignored in main file">,
|
||||
InGroup<DiagGroup<"pragma-system-header-outside-header">>;
|
||||
|
||||
def err_pragma_include_instead_not_sysheader : Error<
|
||||
"'#pragma clang include_instead' cannot be used outside of system headers">;
|
||||
def err_pragma_include_instead_system_reserved : Error<
|
||||
"header '%0' is an implementation detail; #include %select{'%2'|either '%2' or '%3'|one of %2}1 instead">;
|
||||
|
||||
def pp_poisoning_existing_macro : Warning<"poisoning existing macro">;
|
||||
def pp_out_of_date_dependency : Warning<
|
||||
"current file is older than dependency %0">;
|
||||
|
|
|
@ -20,9 +20,12 @@
|
|||
#include "clang/Lex/ModuleMap.h"
|
||||
#include "llvm/ADT/ArrayRef.h"
|
||||
#include "llvm/ADT/DenseMap.h"
|
||||
#include "llvm/ADT/SetVector.h"
|
||||
#include "llvm/ADT/SmallSet.h"
|
||||
#include "llvm/ADT/SmallString.h"
|
||||
#include "llvm/ADT/StringMap.h"
|
||||
#include "llvm/ADT/StringSet.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include "llvm/ADT/StringSet.h"
|
||||
#include "llvm/Support/Allocator.h"
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
|
@ -110,6 +113,14 @@ struct HeaderFileInfo {
|
|||
/// of the framework.
|
||||
StringRef Framework;
|
||||
|
||||
/// List of aliases that this header is known as.
|
||||
/// Most headers should only have at most one alias, but a handful
|
||||
/// have two.
|
||||
llvm::SetVector<llvm::SmallString<32>,
|
||||
llvm::SmallVector<llvm::SmallString<32>, 2>,
|
||||
llvm::SmallSet<llvm::SmallString<32>, 2>>
|
||||
Aliases;
|
||||
|
||||
HeaderFileInfo()
|
||||
: isImport(false), isPragmaOnce(false), DirInfo(SrcMgr::C_User),
|
||||
External(false), isModuleHeader(false), isCompilingModuleHeader(false),
|
||||
|
@ -453,6 +464,10 @@ public:
|
|||
getFileInfo(File).DirInfo = SrcMgr::C_System;
|
||||
}
|
||||
|
||||
void AddFileAlias(const FileEntry *File, StringRef Alias) {
|
||||
getFileInfo(File).Aliases.insert(Alias);
|
||||
}
|
||||
|
||||
/// Mark the specified file as part of a module.
|
||||
void MarkFileModuleHeader(const FileEntry *FE,
|
||||
ModuleMap::ModuleHeaderRole Role,
|
||||
|
|
|
@ -1953,7 +1953,8 @@ public:
|
|||
/// This either returns the EOF token and returns true, or
|
||||
/// pops a level off the include stack and returns false, at which point the
|
||||
/// client should call lex again.
|
||||
bool HandleEndOfFile(Token &Result, bool isEndOfMacro = false);
|
||||
bool HandleEndOfFile(Token &Result, SourceLocation Loc,
|
||||
bool isEndOfMacro = false);
|
||||
|
||||
/// Callback invoked when the current TokenLexer hits the end of its
|
||||
/// token stream.
|
||||
|
@ -2363,12 +2364,14 @@ private:
|
|||
|
||||
// Pragmas.
|
||||
void HandlePragmaDirective(PragmaIntroducer Introducer);
|
||||
void ResolvePragmaIncludeInstead(SourceLocation Location) const;
|
||||
|
||||
public:
|
||||
void HandlePragmaOnce(Token &OnceTok);
|
||||
void HandlePragmaMark(Token &MarkTok);
|
||||
void HandlePragmaPoison();
|
||||
void HandlePragmaSystemHeader(Token &SysHeaderTok);
|
||||
void HandlePragmaIncludeInstead(Token &Tok);
|
||||
void HandlePragmaDependency(Token &DependencyTok);
|
||||
void HandlePragmaPushMacro(Token &Tok);
|
||||
void HandlePragmaPopMacro(Token &Tok);
|
||||
|
|
|
@ -14,11 +14,13 @@
|
|||
#ifndef LLVM_CLANG_LEX_PREPROCESSORLEXER_H
|
||||
#define LLVM_CLANG_LEX_PREPROCESSORLEXER_H
|
||||
|
||||
#include "clang/Basic/SourceLocation.h"
|
||||
#include "clang/Lex/HeaderSearch.h"
|
||||
#include "clang/Lex/MultipleIncludeOpt.h"
|
||||
#include "clang/Lex/Token.h"
|
||||
#include "clang/Basic/SourceLocation.h"
|
||||
#include "llvm/ADT/ArrayRef.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
#include "llvm/ADT/StringMap.h"
|
||||
#include <cassert>
|
||||
|
||||
namespace clang {
|
||||
|
@ -74,6 +76,13 @@ protected:
|
|||
/// we are currently in.
|
||||
SmallVector<PPConditionalInfo, 4> ConditionalStack;
|
||||
|
||||
struct IncludeInfo {
|
||||
const FileEntry *File;
|
||||
SourceLocation Location;
|
||||
};
|
||||
// A complete history of all the files included by the current file.
|
||||
llvm::StringMap<IncludeInfo> IncludeHistory;
|
||||
|
||||
PreprocessorLexer() : FID() {}
|
||||
PreprocessorLexer(Preprocessor *pp, FileID fid);
|
||||
virtual ~PreprocessorLexer() = default;
|
||||
|
@ -175,6 +184,15 @@ public:
|
|||
ConditionalStack.clear();
|
||||
ConditionalStack.append(CL.begin(), CL.end());
|
||||
}
|
||||
|
||||
void addInclude(StringRef Filename, const FileEntry &File,
|
||||
SourceLocation Location) {
|
||||
IncludeHistory.insert({Filename, {&File, Location}});
|
||||
}
|
||||
|
||||
const llvm::StringMap<IncludeInfo> &getIncludeHistory() const {
|
||||
return IncludeHistory;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace clang
|
||||
|
|
|
@ -2811,11 +2811,11 @@ bool Lexer::LexEndOfFile(Token &Result, const char *CurPtr) {
|
|||
ConditionalStack.pop_back();
|
||||
}
|
||||
|
||||
SourceLocation EndLoc = getSourceLocation(BufferEnd);
|
||||
// C99 5.1.1.2p2: If the file is non-empty and didn't end in a newline, issue
|
||||
// a pedwarn.
|
||||
if (CurPtr != BufferStart && (CurPtr[-1] != '\n' && CurPtr[-1] != '\r')) {
|
||||
DiagnosticsEngine &Diags = PP->getDiagnostics();
|
||||
SourceLocation EndLoc = getSourceLocation(BufferEnd);
|
||||
unsigned DiagID;
|
||||
|
||||
if (LangOpts.CPlusPlus11) {
|
||||
|
@ -2838,7 +2838,7 @@ bool Lexer::LexEndOfFile(Token &Result, const char *CurPtr) {
|
|||
BufferPtr = CurPtr;
|
||||
|
||||
// Finally, let the preprocessor handle this.
|
||||
return PP->HandleEndOfFile(Result, isPragmaLexer());
|
||||
return PP->HandleEndOfFile(Result, EndLoc, isPragmaLexer());
|
||||
}
|
||||
|
||||
/// isNextPPTokenLParen - Return 1 if the next unexpanded token lexed from
|
||||
|
|
|
@ -2022,6 +2022,12 @@ Preprocessor::ImportAction Preprocessor::HandleHeaderIncludeOrImport(
|
|||
IsFrameworkFound, IsImportDecl, IsMapped, LookupFrom, LookupFromFile,
|
||||
LookupFilename, RelativePath, SearchPath, SuggestedModule, isAngled);
|
||||
|
||||
// Record the header's filename for later use.
|
||||
if (File)
|
||||
CurLexer->addInclude(
|
||||
{FilenameTok.getLiteralData(), FilenameTok.getLength()},
|
||||
File->getFileEntry(), FilenameLoc);
|
||||
|
||||
if (usingPCHWithThroughHeader() && SkippingUntilPCHThroughHeader) {
|
||||
if (File && isPCHThroughHeader(&File->getFileEntry()))
|
||||
SkippingUntilPCHThroughHeader = false;
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "clang/Basic/FileManager.h"
|
||||
#include "clang/Basic/SourceLocation.h"
|
||||
#include "clang/Basic/SourceManager.h"
|
||||
#include "clang/Lex/HeaderSearch.h"
|
||||
#include "clang/Lex/LexDiagnostic.h"
|
||||
|
@ -22,6 +23,7 @@
|
|||
#include "llvm/Support/FileSystem.h"
|
||||
#include "llvm/Support/MemoryBufferRef.h"
|
||||
#include "llvm/Support/Path.h"
|
||||
|
||||
using namespace clang;
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
@ -299,10 +301,46 @@ void Preprocessor::diagnoseMissingHeaderInUmbrellaDir(const Module &Mod) {
|
|||
}
|
||||
}
|
||||
|
||||
void Preprocessor::ResolvePragmaIncludeInstead(
|
||||
const SourceLocation Location) const {
|
||||
assert(Location.isValid());
|
||||
if (CurLexer == nullptr)
|
||||
return;
|
||||
|
||||
if (SourceMgr.isInSystemHeader(Location))
|
||||
return;
|
||||
|
||||
for (const auto &Include : CurLexer->getIncludeHistory()) {
|
||||
StringRef Filename = Include.getKey();
|
||||
const PreprocessorLexer::IncludeInfo &Info = Include.getValue();
|
||||
ArrayRef<SmallString<32>> Aliases =
|
||||
HeaderInfo.getFileInfo(Info.File).Aliases.getArrayRef();
|
||||
|
||||
if (Aliases.empty())
|
||||
continue;
|
||||
|
||||
switch (Aliases.size()) {
|
||||
case 1:
|
||||
Diag(Info.Location, diag::err_pragma_include_instead_system_reserved)
|
||||
<< Filename << 0 << Aliases[0];
|
||||
continue;
|
||||
case 2:
|
||||
Diag(Info.Location, diag::err_pragma_include_instead_system_reserved)
|
||||
<< Filename << 1 << Aliases[0] << Aliases[1];
|
||||
continue;
|
||||
default: {
|
||||
Diag(Info.Location, diag::err_pragma_include_instead_system_reserved)
|
||||
<< Filename << 2 << ("{'" + llvm::join(Aliases, "', '") + "'}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// HandleEndOfFile - This callback is invoked when the lexer hits the end of
|
||||
/// the current file. This either returns the EOF token or pops a level off
|
||||
/// the include stack and keeps going.
|
||||
bool Preprocessor::HandleEndOfFile(Token &Result, bool isEndOfMacro) {
|
||||
bool Preprocessor::HandleEndOfFile(Token &Result, SourceLocation EndLoc,
|
||||
bool isEndOfMacro) {
|
||||
assert(!CurTokenLexer &&
|
||||
"Ending a file when currently in a macro!");
|
||||
|
||||
|
@ -372,6 +410,9 @@ bool Preprocessor::HandleEndOfFile(Token &Result, bool isEndOfMacro) {
|
|||
}
|
||||
}
|
||||
|
||||
if (EndLoc.isValid())
|
||||
ResolvePragmaIncludeInstead(EndLoc);
|
||||
|
||||
// Complain about reaching a true EOF within arc_cf_code_audited.
|
||||
// We don't want to complain about reaching the end of a macro
|
||||
// instantiation or a _Pragma.
|
||||
|
@ -560,7 +601,7 @@ bool Preprocessor::HandleEndOfTokenLexer(Token &Result) {
|
|||
TokenLexerCache[NumCachedTokenLexers++] = std::move(CurTokenLexer);
|
||||
|
||||
// Handle this like a #include file being popped off the stack.
|
||||
return HandleEndOfFile(Result, true);
|
||||
return HandleEndOfFile(Result, {}, true);
|
||||
}
|
||||
|
||||
/// RemoveTopOfLexerStack - Pop the current lexer/macro exp off the top of the
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
#include "clang/Lex/Pragma.h"
|
||||
#include "clang/Basic/Diagnostic.h"
|
||||
#include "clang/Basic/DiagnosticLex.h"
|
||||
#include "clang/Basic/FileManager.h"
|
||||
#include "clang/Basic/IdentifierTable.h"
|
||||
#include "clang/Basic/LLVM.h"
|
||||
|
@ -35,11 +36,12 @@
|
|||
#include "clang/Lex/TokenLexer.h"
|
||||
#include "llvm/ADT/ArrayRef.h"
|
||||
#include "llvm/ADT/DenseMap.h"
|
||||
#include "llvm/ADT/Optional.h"
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
#include "llvm/ADT/SmallString.h"
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
#include "llvm/ADT/StringSwitch.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include "llvm/ADT/StringSwitch.h"
|
||||
#include "llvm/Support/Compiler.h"
|
||||
#include "llvm/Support/ErrorHandling.h"
|
||||
#include "llvm/Support/Timer.h"
|
||||
|
@ -495,43 +497,88 @@ void Preprocessor::HandlePragmaSystemHeader(Token &SysHeaderTok) {
|
|||
SrcMgr::C_System);
|
||||
}
|
||||
|
||||
/// HandlePragmaDependency - Handle \#pragma GCC dependency "foo" blah.
|
||||
void Preprocessor::HandlePragmaDependency(Token &DependencyTok) {
|
||||
static llvm::Optional<Token> LexHeader(Preprocessor &PP,
|
||||
Optional<FileEntryRef> &File,
|
||||
bool SuppressIncludeNotFoundError) {
|
||||
Token FilenameTok;
|
||||
if (LexHeaderName(FilenameTok, /*AllowConcatenation*/false))
|
||||
return;
|
||||
if (PP.LexHeaderName(FilenameTok, /*AllowConcatenation*/ false))
|
||||
return llvm::None;
|
||||
|
||||
// If the next token wasn't a header-name, diagnose the error.
|
||||
if (FilenameTok.isNot(tok::header_name)) {
|
||||
Diag(FilenameTok.getLocation(), diag::err_pp_expects_filename);
|
||||
return;
|
||||
PP.Diag(FilenameTok.getLocation(), diag::err_pp_expects_filename);
|
||||
return llvm::None;
|
||||
}
|
||||
|
||||
// Reserve a buffer to get the spelling.
|
||||
SmallString<128> FilenameBuffer;
|
||||
bool Invalid = false;
|
||||
StringRef Filename = getSpelling(FilenameTok, FilenameBuffer, &Invalid);
|
||||
StringRef Filename = PP.getSpelling(FilenameTok, FilenameBuffer, &Invalid);
|
||||
if (Invalid)
|
||||
return;
|
||||
return llvm::None;
|
||||
|
||||
bool isAngled =
|
||||
GetIncludeFilenameSpelling(FilenameTok.getLocation(), Filename);
|
||||
PP.GetIncludeFilenameSpelling(FilenameTok.getLocation(), Filename);
|
||||
// If GetIncludeFilenameSpelling set the start ptr to null, there was an
|
||||
// error.
|
||||
if (Filename.empty())
|
||||
return;
|
||||
return llvm::None;
|
||||
|
||||
// Search include directories for this file.
|
||||
const DirectoryLookup *CurDir;
|
||||
Optional<FileEntryRef> File =
|
||||
LookupFile(FilenameTok.getLocation(), Filename, isAngled, nullptr,
|
||||
nullptr, CurDir, nullptr, nullptr, nullptr, nullptr, nullptr);
|
||||
File = PP.LookupFile(FilenameTok.getLocation(), Filename, isAngled, nullptr,
|
||||
nullptr, CurDir, nullptr, nullptr, nullptr, nullptr,
|
||||
nullptr);
|
||||
if (!File) {
|
||||
if (!SuppressIncludeNotFoundError)
|
||||
Diag(FilenameTok, diag::err_pp_file_not_found) << Filename;
|
||||
PP.Diag(FilenameTok, diag::err_pp_file_not_found) << Filename;
|
||||
return llvm::None;
|
||||
}
|
||||
|
||||
return FilenameTok;
|
||||
}
|
||||
|
||||
/// HandlePragmaIncludeInstead - Handle \#pragma clang include_instead(header).
|
||||
void Preprocessor::HandlePragmaIncludeInstead(Token &Tok) {
|
||||
// Get the current file lexer we're looking at. Ignore _Pragma 'files' etc.
|
||||
PreprocessorLexer *TheLexer = getCurrentFileLexer();
|
||||
|
||||
if (!SourceMgr.isInSystemHeader(Tok.getLocation())) {
|
||||
Diag(Tok, diag::err_pragma_include_instead_not_sysheader);
|
||||
return;
|
||||
}
|
||||
|
||||
Lex(Tok);
|
||||
if (Tok.isNot(tok::l_paren)) {
|
||||
Diag(Tok, diag::err_expected) << "(";
|
||||
return;
|
||||
}
|
||||
|
||||
Optional<FileEntryRef> File;
|
||||
llvm::Optional<Token> FilenameTok =
|
||||
LexHeader(*this, File, SuppressIncludeNotFoundError);
|
||||
if (!FilenameTok)
|
||||
return;
|
||||
|
||||
Lex(Tok);
|
||||
if (Tok.isNot(tok::r_paren)) {
|
||||
Diag(Tok, diag::err_expected) << ")";
|
||||
return;
|
||||
}
|
||||
|
||||
HeaderInfo.AddFileAlias(
|
||||
TheLexer->getFileEntry(),
|
||||
{FilenameTok->getLiteralData(), FilenameTok->getLength()});
|
||||
}
|
||||
|
||||
/// HandlePragmaDependency - Handle \#pragma GCC dependency "foo" blah.
|
||||
void Preprocessor::HandlePragmaDependency(Token &DependencyTok) {
|
||||
Optional<FileEntryRef> File;
|
||||
llvm::Optional<Token> FilenameTok =
|
||||
LexHeader(*this, File, SuppressIncludeNotFoundError);
|
||||
if (!FilenameTok)
|
||||
return;
|
||||
|
||||
const FileEntry *CurFile = getCurrentFileLexer()->getFileEntry();
|
||||
|
||||
// If this file is older than the file it depends on, emit a diagnostic.
|
||||
|
@ -547,7 +594,7 @@ void Preprocessor::HandlePragmaDependency(Token &DependencyTok) {
|
|||
// Remove the trailing ' ' if present.
|
||||
if (!Message.empty())
|
||||
Message.erase(Message.end()-1);
|
||||
Diag(FilenameTok, diag::pp_out_of_date_dependency) << Message;
|
||||
Diag(*FilenameTok, diag::pp_out_of_date_dependency) << Message;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1022,6 +1069,18 @@ struct PragmaSystemHeaderHandler : public PragmaHandler {
|
|||
}
|
||||
};
|
||||
|
||||
/// PragmaIncludeInsteadHandler - "\#pragma clang include_instead(header)" marks
|
||||
/// the current file as non-includable if the including header is not a system
|
||||
/// header.
|
||||
struct PragmaIncludeInsteadHandler : public PragmaHandler {
|
||||
PragmaIncludeInsteadHandler() : PragmaHandler("include_instead") {}
|
||||
|
||||
void HandlePragma(Preprocessor &PP, PragmaIntroducer Introducer,
|
||||
Token &IIToken) override {
|
||||
PP.HandlePragmaIncludeInstead(IIToken);
|
||||
}
|
||||
};
|
||||
|
||||
struct PragmaDependencyHandler : public PragmaHandler {
|
||||
PragmaDependencyHandler() : PragmaHandler("dependency") {}
|
||||
|
||||
|
@ -1934,6 +1993,7 @@ void Preprocessor::RegisterBuiltinPragmas() {
|
|||
// #pragma clang ...
|
||||
AddPragmaHandler("clang", new PragmaPoisonHandler());
|
||||
AddPragmaHandler("clang", new PragmaSystemHeaderHandler());
|
||||
AddPragmaHandler("clang", new PragmaIncludeInsteadHandler());
|
||||
AddPragmaHandler("clang", new PragmaDebugHandler());
|
||||
AddPragmaHandler("clang", new PragmaDependencyHandler());
|
||||
AddPragmaHandler("clang", new PragmaDiagnosticHandler("clang"));
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
#pragma GCC system_header
|
||||
|
||||
#pragma clang include_instead <include_instead/public-before.h>
|
||||
// expected-error@-1{{expected (}}
|
||||
|
||||
#pragma clang include_instead(<include_instead/public-after.h>]
|
||||
// expected-error@-1{{expected )}}
|
|
@ -0,0 +1,3 @@
|
|||
#pragma GCC system_header
|
||||
#pragma clang include_instead(<include_instead/does_not_exist.h>)
|
||||
// expected-error@-1{{'include_instead/does_not_exist.h' file not found}}
|
|
@ -0,0 +1,2 @@
|
|||
#pragma clang include_instead(<include_instead/public1.h>)
|
||||
// expected-error@-1{{'#pragma clang include_instead' cannot be used outside of system headers}}
|
|
@ -0,0 +1,4 @@
|
|||
#include <include_instead/private1.h>
|
||||
|
||||
#pragma GCC system_header
|
||||
#pragma clang include_instead(<include_instead/public-gcc-system-header-before-includes.h>)
|
|
@ -0,0 +1,2 @@
|
|||
#pragma GCC system_header
|
||||
#pragma clang include_instead(<include_instead/public-before.h>)
|
|
@ -0,0 +1,4 @@
|
|||
#pragma GCC system_header
|
||||
|
||||
#pragma clang include_instead(<include_instead/public-before.h>)
|
||||
#pragma clang include_instead("include_instead/public-after.h")
|
|
@ -0,0 +1,5 @@
|
|||
#pragma GCC system_header
|
||||
|
||||
#pragma clang include_instead(<include_instead/public-after.h>)
|
||||
#pragma clang include_instead(<include_instead/public-empty.h>)
|
||||
#pragma clang include_instead("include_instead/public-before.h")
|
|
@ -0,0 +1,2 @@
|
|||
#include <include_instead/private2.h>
|
||||
#pragma GCC system_header
|
|
@ -0,0 +1,5 @@
|
|||
#pragma GCC system_header
|
||||
|
||||
#include <include_instead/private1.h> // no warning expected
|
||||
#include <include_instead/private2.h> // no warning expected
|
||||
#include <include_instead/private3.h> // no warning expected
|
|
@ -0,0 +1 @@
|
|||
// This file simply needs to exist.
|
|
@ -0,0 +1,16 @@
|
|||
// RUN: %clang_cc1 -fsyntax-only -verify -I %S/Inputs %s
|
||||
|
||||
#include <include_instead/bad-syntax.h>
|
||||
#include <include_instead/non-system-header.h>
|
||||
|
||||
#include <include_instead/private1.h>
|
||||
// expected-error@-1{{header '<include_instead/private1.h>' is an implementation detail; #include '<include_instead/public-before.h>' instead}}
|
||||
|
||||
#include "include_instead/private2.h"
|
||||
// expected-error@-1{{header '"include_instead/private2.h"' is an implementation detail; #include either '<include_instead/public-before.h>' or '"include_instead/public-after.h"' instead}}
|
||||
|
||||
#include <include_instead/private3.h>
|
||||
// expected-error@-1{{header '<include_instead/private3.h>' is an implementation detail; #include one of {'<include_instead/public-after.h>', '<include_instead/public-empty.h>', '"include_instead/public-before.h"'} instead}}
|
||||
|
||||
#include <include_instead/public-before.h>
|
||||
#include <include_instead/public-after.h>
|
|
@ -0,0 +1,2 @@
|
|||
// RUN: %clang_cc1 -fsyntax-only -verify -I %S/Inputs %s
|
||||
#include <include_instead/file-not-found.h>
|
Loading…
Reference in New Issue