diff --git a/clang/include/clang/Lex/HeaderSearch.h b/clang/include/clang/Lex/HeaderSearch.h --- a/clang/include/clang/Lex/HeaderSearch.h +++ b/clang/include/clang/Lex/HeaderSearch.h @@ -89,9 +89,6 @@ /// Whether this file has been looked up as a header. unsigned IsValid : 1; - /// The number of times the file has been included already. - unsigned short NumIncludes = 0; - /// The ID number of the controlling macro. /// /// This ID number will be non-zero when there is a controlling @@ -130,13 +127,6 @@ /// any. const IdentifierInfo * getControllingMacro(ExternalPreprocessorSource *External); - - /// Determine whether this is a non-default header file info, e.g., - /// it corresponds to an actual header we've included or tried to include. - bool isNonDefault() const { - return isImport || isPragmaOnce || NumIncludes || ControllingMacro || - ControllingMacroID; - } }; /// An external source of header file information, which may supply @@ -481,12 +471,6 @@ ModuleMap::ModuleHeaderRole Role, bool isCompilingModuleHeader); - /// Increment the count for the number of times the specified - /// FileEntry has been entered. - void IncrementIncludeCount(const FileEntry *File) { - ++getFileInfo(File).NumIncludes; - } - /// Mark the specified file as having a controlling macro. /// /// This is used by the multiple-include optimization to eliminate @@ -496,11 +480,6 @@ getFileInfo(File).ControllingMacro = ControllingMacro; } - /// Return true if this is the first time encountering this header. - bool FirstTimeLexingFile(const FileEntry *File) { - return getFileInfo(File).NumIncludes == 1; - } - /// Determine whether this file is intended to be safe from /// multiple inclusions, e.g., it has \#pragma once or a controlling /// macro. diff --git a/clang/include/clang/Lex/Preprocessor.h b/clang/include/clang/Lex/Preprocessor.h --- a/clang/include/clang/Lex/Preprocessor.h +++ b/clang/include/clang/Lex/Preprocessor.h @@ -717,6 +717,61 @@ /// This mapping lives within the \p CurSubmoduleState. using MacroMap = llvm::DenseMap; + /// Efficient map with FileEntry keys. + template class FileEntryMap { + /// The underlying storage. Entries are indexed by FileEntry UID. + std::vector UIDToValue; + + public: + /// Create an empty FileEntry map. + FileEntryMap() = default; + + /// Get the value for the given FileEntry key. + T &operator[](const FileEntry *File) { return operator[](File->getUID()); } + + /// Get the value for the given FileEntry UID. + T &operator[](unsigned UID) { + ensureContains(UID); + return UIDToValue[UID]; + } + + /// Get the largest stored UID. + size_t maxUID() const { return UIDToValue.size() - 1; } + + /// Get all FileEntry-value pairs stored in the map. + std::vector> getAll(FileManager &FM) const { + llvm::SmallVector UIDToFileEntry; + FM.GetUniqueIDMapping(UIDToFileEntry); + + std::vector> Result; + for (unsigned UID = 0; UID < UIDToValue.size(); ++UID) + if (UIDToValue[UID] > 0) + Result.emplace_back(UIDToFileEntry[UID], UIDToValue[UID]); + return Result; + } + + private: + /// Determine whether the underlying storage contains a value for FileEntry + /// with the given UID. + bool contains(unsigned UID) const { return UID < UIDToValue.size(); } + + /// Ensure the storage contains a value for FileEntry with the given UID. + void ensureContains(unsigned UID) { + if (!contains(UID)) + UIDToValue.resize(UID + 1); + } + }; + + /// In each submodule, we track the number of includes of a header file and + /// the set of visible modules. + struct SubmoduleIncludeState { + /// Map between FileEntries and the number of times the file was included. + FileEntryMap NumIncludes; + + /// The set of modules that are visible within the submodule. + VisibleModuleSet VisibleModules; + }; + struct SubmoduleState; /// Information about a submodule that we're currently building. @@ -736,12 +791,17 @@ /// The number of pending module macro names when we started building this. unsigned OuterPendingModuleMacroNames; + /// The previous SubmoduleIncludeState. + SubmoduleIncludeState *OuterSubmoduleIncludeState; + BuildingSubmoduleInfo(Module *M, SourceLocation ImportLoc, bool IsPragma, SubmoduleState *OuterSubmoduleState, - unsigned OuterPendingModuleMacroNames) + unsigned OuterPendingModuleMacroNames, + SubmoduleIncludeState *OuterSubmoduleIncludeState) : M(M), ImportLoc(ImportLoc), IsPragma(IsPragma), OuterSubmoduleState(OuterSubmoduleState), - OuterPendingModuleMacroNames(OuterPendingModuleMacroNames) {} + OuterPendingModuleMacroNames(OuterPendingModuleMacroNames), + OuterSubmoduleIncludeState(OuterSubmoduleIncludeState) {} }; SmallVector BuildingSubmoduleStack; @@ -765,6 +825,20 @@ /// in a submodule. SubmoduleState *CurSubmoduleState; + /// The include state for each entered submodule. + std::map SubmoduleIncludeStates; + + /// The include state for preprocessing outside of any submodule. + SubmoduleIncludeState NullSubmoduleIncludeState; + + /// The current include state. Will be \p NullSubmoduleIncludeState if we're + /// not in a submodule. + SubmoduleIncludeState *CurSubmoduleIncludeState; + + /// The global preprocessor state tracking the number of includes across + /// submodules. + FileEntryMap NumIncludes; + /// The set of known macros exported from modules. llvm::FoldingSet ModuleMacros; @@ -1224,6 +1298,31 @@ /// \} + /// Return the number of times the given file has been entered. + unsigned GetNumIncludes(const FileEntry *File); + + /// Return true if this is the first time encountering this header. + bool FirstTimeLexingFile(const FileEntry *File); + + /// Increment the count for the number of times the specified + /// FileEntry has been entered. + void IncrementIncludeCount(const FileEntry *File, unsigned Count = 1); + + /// Get the submodule include information. + const std::map & + getSubmoduleIncludeStates() const { + return SubmoduleIncludeStates; + } + + /// Get the null include information. + // TODO: Merge this into \c getSubmoduleIncludeStates(). + const SubmoduleIncludeState &getNullSubmoduleIncludeState() const { + return NullSubmoduleIncludeState; + } + + /// Add a submodule include. + void addSubmoduleInclude(Module *M, StringRef Filename, unsigned NumIncludes); + /// Return the name of the macro defined before \p Loc that has /// spelling \p Tokens. If there are multiple macros with same spelling, /// return the last one defined. diff --git a/clang/include/clang/Serialization/ASTBitCodes.h b/clang/include/clang/Serialization/ASTBitCodes.h --- a/clang/include/clang/Serialization/ASTBitCodes.h +++ b/clang/include/clang/Serialization/ASTBitCodes.h @@ -695,6 +695,9 @@ /// Record code for \#pragma float_control options. FLOAT_CONTROL_PRAGMA_OPTIONS = 65, + + /// Record code for submodule includes. + SUBMODULE_INCLUDES = 66, }; /// Record types used within a source manager block. diff --git a/clang/include/clang/Serialization/ASTReader.h b/clang/include/clang/Serialization/ASTReader.h --- a/clang/include/clang/Serialization/ASTReader.h +++ b/clang/include/clang/Serialization/ASTReader.h @@ -855,6 +855,11 @@ /// The pragma clang optimize location (if the pragma state is "off"). SourceLocation OptimizeOffPragmaLocation; + /// The submodule include maps. Translates local submodule ID to the map that + /// contains the number of includes for a file. + // TODO: Use plain vector, submodule IDs are small and dense. + llvm::DenseMap> SubmoduleImports; + /// The PragmaMSStructKind pragma ms_struct state if set, or -1. int PragmaMSStructState = -1; diff --git a/clang/lib/Lex/HeaderSearch.cpp b/clang/lib/Lex/HeaderSearch.cpp --- a/clang/lib/Lex/HeaderSearch.cpp +++ b/clang/lib/Lex/HeaderSearch.cpp @@ -92,9 +92,9 @@ unsigned NumOnceOnlyFiles = 0, MaxNumIncludes = 0, NumSingleIncludedFiles = 0; for (unsigned i = 0, e = FileInfo.size(); i != e; ++i) { NumOnceOnlyFiles += (FileInfo[i].isPragmaOnce || FileInfo[i].isImport); - if (MaxNumIncludes < FileInfo[i].NumIncludes) - MaxNumIncludes = FileInfo[i].NumIncludes; - NumSingleIncludedFiles += FileInfo[i].NumIncludes == 1; +// if (MaxNumIncludes < FileInfo[i].NumIncludes) +// MaxNumIncludes = FileInfo[i].NumIncludes; +// NumSingleIncludedFiles += FileInfo[i].NumIncludes == 1; } llvm::errs() << " " << NumOnceOnlyFiles << " #import/#pragma once files.\n" << " " << NumSingleIncludedFiles << " included exactly once.\n" @@ -1203,7 +1203,6 @@ HFI.isImport |= OtherHFI.isImport; HFI.isPragmaOnce |= OtherHFI.isPragmaOnce; HFI.isModuleHeader |= OtherHFI.isModuleHeader; - HFI.NumIncludes += OtherHFI.NumIncludes; if (!HFI.ControllingMacro && !HFI.ControllingMacroID) { HFI.ControllingMacro = OtherHFI.ControllingMacro; @@ -1361,7 +1360,7 @@ FileInfo.isImport = true; // Has this already been #import'ed or #include'd? - if (FileInfo.NumIncludes && !TryEnterImported()) + if (PP.GetNumIncludes(File) && !TryEnterImported()) return false; } else { // Otherwise, if this is a #include of a file that was previously #import'd @@ -1385,7 +1384,7 @@ } // Increment the number of times this file has been included. - ++FileInfo.NumIncludes; + PP.IncrementIncludeCount(File); return true; } diff --git a/clang/lib/Lex/PPDirectives.cpp b/clang/lib/Lex/PPDirectives.cpp --- a/clang/lib/Lex/PPDirectives.cpp +++ b/clang/lib/Lex/PPDirectives.cpp @@ -2051,7 +2051,7 @@ // include cycle. Don't enter already processed files again as it can lead to // reaching the max allowed include depth again. if (Action == Enter && HasReachedMaxIncludeDepth && File && - HeaderInfo.getFileInfo(&File->getFileEntry()).NumIncludes) + GetNumIncludes(*File)) Action = IncludeLimitReached; // Determine whether we should try to import the module for this #include, if 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 @@ -376,8 +376,7 @@ if (const IdentifierInfo *DefinedMacro = CurPPLexer->MIOpt.GetDefinedMacro()) { if (!isMacroDefined(ControllingMacro) && - DefinedMacro != ControllingMacro && - HeaderInfo.FirstTimeLexingFile(FE)) { + DefinedMacro != ControllingMacro && FirstTimeLexingFile(FE)) { // If the edit distance between the two macros is more than 50%, // DefinedMacro may not be header guard, or can be header guard of @@ -688,9 +687,12 @@ bool ForPragma) { if (!getLangOpts().ModulesLocalVisibility) { // Just track that we entered this submodule. - BuildingSubmoduleStack.push_back( - BuildingSubmoduleInfo(M, ImportLoc, ForPragma, CurSubmoduleState, - PendingModuleMacroNames.size())); + BuildingSubmoduleStack.push_back(BuildingSubmoduleInfo( + M, ImportLoc, ForPragma, CurSubmoduleState, + PendingModuleMacroNames.size(), CurSubmoduleIncludeState)); + + CurSubmoduleIncludeState = &SubmoduleIncludeStates[M]; + if (Callbacks) Callbacks->EnteredSubmodule(M, ImportLoc, ForPragma); return; @@ -733,15 +735,16 @@ } // Track that we entered this module. - BuildingSubmoduleStack.push_back( - BuildingSubmoduleInfo(M, ImportLoc, ForPragma, CurSubmoduleState, - PendingModuleMacroNames.size())); + BuildingSubmoduleStack.push_back(BuildingSubmoduleInfo( + M, ImportLoc, ForPragma, CurSubmoduleState, + PendingModuleMacroNames.size(), CurSubmoduleIncludeState)); if (Callbacks) Callbacks->EnteredSubmodule(M, ImportLoc, ForPragma); // Switch to this submodule as the current submodule. CurSubmoduleState = &State; + CurSubmoduleIncludeState = &SubmoduleIncludeStates[M]; // This module is visible to itself. if (FirstTime) @@ -773,6 +776,8 @@ Module *LeavingMod = Info.M; SourceLocation ImportLoc = Info.ImportLoc; + CurSubmoduleIncludeState = Info.OuterSubmoduleIncludeState; + if (!needModuleMacros() || (!getLangOpts().ModulesLocalVisibility && LeavingMod->getTopLevelModuleName() != getLangOpts().CurrentModule)) { diff --git a/clang/lib/Lex/Preprocessor.cpp b/clang/lib/Lex/Preprocessor.cpp --- a/clang/lib/Lex/Preprocessor.cpp +++ b/clang/lib/Lex/Preprocessor.cpp @@ -91,7 +91,8 @@ // deferred to Preprocessor::Initialize(). Identifiers(IILookup), PragmaHandlers(new PragmaNamespace(StringRef())), TUKind(TUKind), SkipMainFilePreamble(0, true), - CurSubmoduleState(&NullSubmoduleState) { + CurSubmoduleState(&NullSubmoduleState), + CurSubmoduleIncludeState(&NullSubmoduleIncludeState) { OwnsHeaderSearch = OwnsHeaders; // Default to discarding comments. @@ -549,7 +550,7 @@ // Tell the header info that the main file was entered. If the file is later // #imported, it won't be re-entered. if (const FileEntry *FE = SourceMgr.getFileEntryForID(MainFileID)) - HeaderInfo.IncrementIncludeCount(FE); + IncrementIncludeCount(FE); } // Preprocess Predefines to populate the initial preprocessor state. @@ -1315,6 +1316,17 @@ << Message; }); + CurSubmoduleIncludeState->VisibleModules.setVisible( + M, Loc, + [&](Module *M) { + auto &ModNumIncludes = SubmoduleIncludeStates[M].NumIncludes; + for (unsigned UID = 0; UID <= ModNumIncludes.maxUID(); ++UID) { + CurSubmoduleIncludeState->NumIncludes[UID] += ModNumIncludes[UID]; + NumIncludes[UID] += ModNumIncludes[UID]; + } + }, + [](ArrayRef, Module *, StringRef) {}); + // Add this module to the imports list of the currently-built submodule. if (!BuildingSubmoduleStack.empty() && M != BuildingSubmoduleStack.back().M) BuildingSubmoduleStack.back().M->Imports.insert(M); @@ -1466,3 +1478,23 @@ Record = new PreprocessingRecord(getSourceManager()); addPPCallbacks(std::unique_ptr(Record)); } + +unsigned Preprocessor::GetNumIncludes(const FileEntry *File) { + return NumIncludes[File]; +} + +bool Preprocessor::FirstTimeLexingFile(const FileEntry *File) { + return GetNumIncludes(File) == 1; +} + +void Preprocessor::IncrementIncludeCount(const FileEntry *File, unsigned Count) { + CurSubmoduleIncludeState->NumIncludes[File] += Count; + NumIncludes[File] += Count; +} + +void Preprocessor::addSubmoduleInclude(Module *M, StringRef Filename, + unsigned Count) { + if (auto MaybeFE = FileMgr.getFile(Filename)) + (M == nullptr ? NumIncludes + : SubmoduleIncludeStates[M].NumIncludes)[*MaybeFE] += Count; +} diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp --- a/clang/lib/Serialization/ASTReader.cpp +++ b/clang/lib/Serialization/ASTReader.cpp @@ -1888,10 +1888,6 @@ HFI.isPragmaOnce |= (Flags >> 4) & 0x01; HFI.DirInfo = (Flags >> 1) & 0x07; HFI.IndexHeaderMapHeader = Flags & 0x01; - // FIXME: Find a better way to handle this. Maybe just store a - // "has been included" flag? - HFI.NumIncludes = std::max(endian::readNext(d), - HFI.NumIncludes); HFI.ControllingMacroID = Reader.getGlobalIdentifierID( M, endian::readNext(d)); if (unsigned FrameworkOffset = @@ -3701,6 +3697,19 @@ break; } + case SUBMODULE_INCLUDES: { + unsigned LocalSMID = Record[0]; + unsigned NumIncludes = Record[1]; + StringRef Filename = Blob; + + // PCHs don't have submodules. We can add the includes right away. + if (PP.getPreprocessorOpts().ImplicitPCHInclude == F.FileName) + PP.addSubmoduleInclude(nullptr, Filename, NumIncludes); + else + SubmoduleImports[LocalSMID][Filename] = NumIncludes; + break; + } + case LATE_PARSED_TEMPLATE: LateParsedTemplates.emplace_back( std::piecewise_construct, std::forward_as_tuple(&F), @@ -5569,6 +5578,21 @@ CurrentModule->IsUnimportable = ParentModule && ParentModule->IsUnimportable; CurrentModule->IsAvailable = !CurrentModule->IsUnimportable; + + // Now that we can map the local submodule ID to an actual Module + // instance, pass the include information to the preprocessor. + // TODO: Avoid iteration here. + for (const auto &Submodule : SubmoduleImports) { + unsigned LocalID = Submodule.getFirst(); + if (GlobalID == getGlobalSubmoduleID(F, LocalID)) { + for (const auto &Include : Submodule.getSecond()) { + PP.addSubmoduleInclude(CurrentModule, Include.getKey(), + Include.getValue()); + } + break; + } + } + break; } diff --git a/clang/lib/Serialization/ASTWriter.cpp b/clang/lib/Serialization/ASTWriter.cpp --- a/clang/lib/Serialization/ASTWriter.cpp +++ b/clang/lib/Serialization/ASTWriter.cpp @@ -116,6 +116,8 @@ #include #include +#include + using namespace clang; using namespace clang::serialization; @@ -1696,7 +1698,7 @@ std::pair EmitKeyDataLength(raw_ostream& Out, key_type_ref key, data_type_ref Data) { unsigned KeyLen = key.Filename.size() + 1 + 8 + 8; - unsigned DataLen = 1 + 2 + 4 + 4; + unsigned DataLen = 1 + 4 + 4; for (auto ModInfo : Data.KnownHeaders) if (Writer.getLocalOrImportedSubmoduleID(ModInfo.getModule())) DataLen += 4; @@ -1728,7 +1730,6 @@ | (Data.HFI.DirInfo << 1) | Data.HFI.IndexHeaderMapHeader; LE.write(Flags); - LE.write(Data.HFI.NumIncludes); if (!Data.HFI.ControllingMacro) LE.write(Data.HFI.ControllingMacroID); @@ -2379,6 +2380,44 @@ MacroOffsetsBase - ASTBlockStartOffset}; Stream.EmitRecordWithBlob(MacroOffsetAbbrev, Record, bytes(MacroOffsets)); } + + Abbrev = std::make_shared(); + Abbrev->Add(BitCodeAbbrevOp(SUBMODULE_INCLUDES)); + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 32)); // submodule ID + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 32)); // # of includes + Abbrev->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Blob)); + + unsigned IncludesAbbrev = Stream.EmitAbbrev(std::move(Abbrev)); + + // TODO: Use plain vector, submodule IDs are small and dense. + std::map> ToBeSerialized; + + auto CollectModuleIncludes = [&](Module *M, const auto &Includes) { + unsigned LocalSMID = getLocalOrImportedSubmoduleID(M); + + for (const auto &Inc : Includes.NumIncludes.getAll(PP.getFileManager())) { + unsigned NumIncludes = Inc.second; + StringRef Filename = Inc.first->getName(); + ToBeSerialized[LocalSMID].insert({Filename, NumIncludes}); + } + }; + + CollectModuleIncludes(const_cast(PP).getCurrentModule(), + PP.getNullSubmoduleIncludeState()); + for (const auto &SubM : PP.getSubmoduleIncludeStates()) + CollectModuleIncludes(SubM.first, SubM.second); + + for (const auto& Include : ToBeSerialized) { + unsigned LocalSMID = Include.first; + + for (const auto& X : Include.second) { + StringRef Filename = X.first; + unsigned NumIncludes = X.second; + RecordData::value_type Record[] = {SUBMODULE_INCLUDES, LocalSMID, + NumIncludes}; + Stream.EmitRecordWithBlob(IncludesAbbrev, Record, Filename); + } + } } void ASTWriter::WritePreprocessorDetail(PreprocessingRecord &PPRec, diff --git a/clang/test/Modules/Inputs/import-submodule-visibility/A.framework/Headers/A.h b/clang/test/Modules/Inputs/import-submodule-visibility/A.framework/Headers/A.h new file mode 100644 --- /dev/null +++ b/clang/test/Modules/Inputs/import-submodule-visibility/A.framework/Headers/A.h @@ -0,0 +1 @@ +#include "Textual.h" diff --git a/clang/test/Modules/Inputs/import-submodule-visibility/A.framework/Modules/module.modulemap b/clang/test/Modules/Inputs/import-submodule-visibility/A.framework/Modules/module.modulemap new file mode 100644 --- /dev/null +++ b/clang/test/Modules/Inputs/import-submodule-visibility/A.framework/Modules/module.modulemap @@ -0,0 +1,3 @@ +framework module A { + header "A.h" +} diff --git a/clang/test/Modules/Inputs/import-submodule-visibility/B.framework/Headers/B1.h b/clang/test/Modules/Inputs/import-submodule-visibility/B.framework/Headers/B1.h new file mode 100644 --- /dev/null +++ b/clang/test/Modules/Inputs/import-submodule-visibility/B.framework/Headers/B1.h @@ -0,0 +1 @@ +#include "Textual.h" diff --git a/clang/test/Modules/Inputs/import-submodule-visibility/B.framework/Headers/B2.h b/clang/test/Modules/Inputs/import-submodule-visibility/B.framework/Headers/B2.h new file mode 100644 diff --git a/clang/test/Modules/Inputs/import-submodule-visibility/B.framework/Modules/module.modulemap b/clang/test/Modules/Inputs/import-submodule-visibility/B.framework/Modules/module.modulemap new file mode 100644 --- /dev/null +++ b/clang/test/Modules/Inputs/import-submodule-visibility/B.framework/Modules/module.modulemap @@ -0,0 +1,4 @@ +framework module B { + module B1 { header "B1.h" } + module B2 { header "B2.h" } +} diff --git a/clang/test/Modules/Inputs/import-submodule-visibility/C/C.h b/clang/test/Modules/Inputs/import-submodule-visibility/C/C.h new file mode 100644 --- /dev/null +++ b/clang/test/Modules/Inputs/import-submodule-visibility/C/C.h @@ -0,0 +1 @@ +#include "Textual.h" diff --git a/clang/test/Modules/Inputs/import-submodule-visibility/C/module.modulemap b/clang/test/Modules/Inputs/import-submodule-visibility/C/module.modulemap new file mode 100644 --- /dev/null +++ b/clang/test/Modules/Inputs/import-submodule-visibility/C/module.modulemap @@ -0,0 +1 @@ +module C { header "C.h" } diff --git a/clang/test/Modules/Inputs/import-submodule-visibility/D/D1.h b/clang/test/Modules/Inputs/import-submodule-visibility/D/D1.h new file mode 100644 --- /dev/null +++ b/clang/test/Modules/Inputs/import-submodule-visibility/D/D1.h @@ -0,0 +1 @@ +#include "Textual.h" diff --git a/clang/test/Modules/Inputs/import-submodule-visibility/D/D2.h b/clang/test/Modules/Inputs/import-submodule-visibility/D/D2.h new file mode 100644 diff --git a/clang/test/Modules/Inputs/import-submodule-visibility/D/module.modulemap b/clang/test/Modules/Inputs/import-submodule-visibility/D/module.modulemap new file mode 100644 --- /dev/null +++ b/clang/test/Modules/Inputs/import-submodule-visibility/D/module.modulemap @@ -0,0 +1,4 @@ +module D { + module D1 { header "D1.h" } + module D2 { header "D2.h" } +} diff --git a/clang/test/Modules/Inputs/import-submodule-visibility/Textual.h b/clang/test/Modules/Inputs/import-submodule-visibility/Textual.h new file mode 100644 --- /dev/null +++ b/clang/test/Modules/Inputs/import-submodule-visibility/Textual.h @@ -0,0 +1 @@ +#define MACRO_TEXTUAL 1 diff --git a/clang/test/Modules/import-submodule-visibility.c b/clang/test/Modules/import-submodule-visibility.c new file mode 100644 --- /dev/null +++ b/clang/test/Modules/import-submodule-visibility.c @@ -0,0 +1,58 @@ +// RUN: rm -rf %t && mkdir -p %t + +// This test checks that imports of headers that appeared in a different submodule than +// what is imported by the current TU don't affect the compilation. + +// Framework "A" includes "Textual.h" in the top-level module. +// This test checks that simply specifying the PCM file on the command line (without actually importing "A") does not +// prevent "Textual.h" to be included in the TU. +// +// RUN: %clang_cc1 -fmodules -I %S/Inputs/import-submodule-visibility \ +// RUN: -emit-module %S/Inputs/import-submodule-visibility/A.framework/Modules/module.modulemap -fmodule-name=A -o %t/A.pcm +// RUN: %clang_cc1 -fmodules -I %S/Inputs/import-submodule-visibility -fsyntax-only %s -DA \ +// RUN: -fmodule-file=%t/A.pcm -fmodule-map-file=%S/Inputs/import-submodule-visibility/A.framework/Modules/module.modulemap + +// Framework "B" includes "Textual.h" in the "B1" submodule and its "B2" submodule does not include "Textual.h" at all. +// This test checks that specifying the PCM file on the command line and importing "B2" in the source does not +// prevent "Textual.h" to be included in the TU. +// +// RUN: %clang_cc1 -fmodules -I %S/Inputs/import-submodule-visibility \ +// RUN: -emit-module %S/Inputs/import-submodule-visibility/B.framework/Modules/module.modulemap -fmodule-name=B -o %t/B.pcm +// RUN: %clang_cc1 -fmodules -I %S/Inputs/import-submodule-visibility -fsyntax-only %s -DB -iframework %S/Inputs/import-submodule-visibility \ +// RUN: -fmodule-file=%t/B.pcm -fmodule-map-file=%S/Inputs/import-submodule-visibility/B.framework/Modules/module.modulemap + +// Module "C" includes "Textual.h" in the top level module. +// This is a module-only version of the test with framework A. +// +// RUN: %clang_cc1 -fmodules -I %S/Inputs/import-submodule-visibility \ +// RUN: -emit-module %S/Inputs/import-submodule-visibility/C/module.modulemap -fmodule-name=C -o %t/C.pcm +// RUN: %clang_cc1 -fmodules -I %S/Inputs/import-submodule-visibility -fsyntax-only %s -DC \ +// RUN: -fmodule-file=%t/C.pcm -fmodule-map-file=%S/Inputs/import-submodule-visibility/C/module.modulemap + +// Module "D" includes "Textual.h" in the "D1" submodule and its "D2" submodules does not include "Textual.h" at all. +// This is a module-only version of the test with framework B. +// +// RUN: %clang_cc1 -fmodules -I %S/Inputs/import-submodule-visibility \ +// RUN: -emit-module %S/Inputs/import-submodule-visibility/D/module.modulemap -fmodule-name=D -o %t/D.pcm +// RUN: %clang_cc1 -fmodules -I %S/Inputs/import-submodule-visibility -fsyntax-only %s -DD \ +// RUN: -fmodule-file=%t/D.pcm -fmodule-map-file=%S/Inputs/import-submodule-visibility/D/module.modulemap + +#ifdef A +// +#endif + +#ifdef B +#import +#endif + +#ifdef C +// +#endif + +#ifdef D +#import "D/D2.h" +#endif + +#import "Textual.h" + +static int x = MACRO_TEXTUAL;