Skip to content

[clang][lex] Introduce new single-module-parse mode #135813

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions clang/include/clang/Driver/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -3247,6 +3247,10 @@ def fno_modules_prune_non_affecting_module_map_files :
Group<f_Group>, Flags<[]>, Visibility<[CC1Option]>,
MarshallingInfoNegativeFlag<HeaderSearchOpts<"ModulesPruneNonAffectingModuleMaps">>,
HelpText<"Do not prune non-affecting module map files when writing module files">;
def fmodules_single_module_parse_mode :
Flag<["-"], "fmodules-single-module-parse-mode">,
Group<f_Group>, Flags<[]>, Visibility<[CC1Option]>,
MarshallingInfoFlag<PreprocessorOpts<"SingleModuleParseMode">>;

def fincremental_extensions :
Flag<["-"], "fincremental-extensions">,
Expand Down
7 changes: 7 additions & 0 deletions clang/include/clang/Lex/PreprocessorOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,13 @@ class PreprocessorOptions {
/// that the client can get the maximum amount of information from the parser.
bool SingleFileParseMode = false;

/// When enabled, preprocessor is in a mode for parsing a single module only.
///
/// Disables imports of other modules and if there are any unresolved
/// identifiers in preprocessor directive conditions it causes all blocks to
/// be skipped so that the client can get a strict subset of the contents.
bool SingleModuleParseMode = false;

/// When enabled, the preprocessor will construct editor placeholder tokens.
bool LexEditorPlaceholders = true;

Expand Down
28 changes: 24 additions & 4 deletions clang/lib/Lex/PPDirectives.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2073,10 +2073,11 @@ void Preprocessor::HandleIncludeDirective(SourceLocation HashLoc,
return;

if (FilenameTok.isNot(tok::header_name)) {
if (FilenameTok.is(tok::identifier) && PPOpts.SingleFileParseMode) {
if (FilenameTok.is(tok::identifier) &&
(PPOpts.SingleFileParseMode || PPOpts.SingleModuleParseMode)) {
// If we saw #include IDENTIFIER and lexing didn't turn in into a header
// name, it was undefined. In 'single-file-parse' mode, just skip the
// directive without emitting diagnostics - the identifier might be
// name, it was undefined. In 'single-{file,module}-parse' mode, just skip
// the directive without emitting diagnostics - the identifier might be
// normally defined in previously-skipped include directive.
DiscardUntilEndOfDirective();
return;
Expand Down Expand Up @@ -2384,10 +2385,15 @@ Preprocessor::ImportAction Preprocessor::HandleHeaderIncludeOrImport(
(getLangOpts().CPlusPlusModules || getLangOpts().Modules) &&
ModuleToImport && !ModuleToImport->isHeaderUnit();

if (MaybeTranslateInclude && (UsableHeaderUnit || UsableClangHeaderModule) &&
PPOpts.SingleModuleParseMode) {
Action = IncludeLimitReached;
}
// Determine whether we should try to import the module for this #include, if
// there is one. Don't do so if precompiled module support is disabled or we
// are processing this module textually (because we're building the module).
if (MaybeTranslateInclude && (UsableHeaderUnit || UsableClangHeaderModule)) {
else if (MaybeTranslateInclude &&
(UsableHeaderUnit || UsableClangHeaderModule)) {
// If this include corresponds to a module but that module is
// unavailable, diagnose the situation and bail out.
// FIXME: Remove this; loadModule does the same check (but produces
Expand Down Expand Up @@ -3439,6 +3445,13 @@ void Preprocessor::HandleIfdefDirective(Token &Result,
CurPPLexer->pushConditionalLevel(DirectiveTok.getLocation(),
/*wasskip*/false, /*foundnonskip*/false,
/*foundelse*/false);
} else if (PPOpts.SingleModuleParseMode && !MI) {
// In 'single-module-parse mode' undefined identifiers trigger skipping of
// all the directive blocks. We lie here and set FoundNonSkipPortion so that
// even any \#else blocks get skipped.
SkipExcludedConditionalBlock(
HashToken.getLocation(), DirectiveTok.getLocation(),
/*FoundNonSkipPortion=*/true, /*FoundElse=*/false);
} else if (!MI == isIfndef || RetainExcludedCB) {
// Yes, remember that we are inside a conditional, then lex the next token.
CurPPLexer->pushConditionalLevel(DirectiveTok.getLocation(),
Expand Down Expand Up @@ -3493,6 +3506,13 @@ void Preprocessor::HandleIfDirective(Token &IfToken,
// the directive blocks.
CurPPLexer->pushConditionalLevel(IfToken.getLocation(), /*wasskip*/false,
/*foundnonskip*/false, /*foundelse*/false);
} else if (PPOpts.SingleModuleParseMode && DER.IncludedUndefinedIds) {
// In 'single-module-parse mode' undefined identifiers trigger skipping of
// all the directive blocks. We lie here and set FoundNonSkipPortion so that
// even any \#else blocks get skipped.
SkipExcludedConditionalBlock(HashToken.getLocation(), IfToken.getLocation(),
/*FoundNonSkipPortion=*/true,
/*FoundElse=*/false);
} else if (ConditionalTrue || RetainExcludedCB) {
// Yes, remember that we are inside a conditional, then lex the next token.
CurPPLexer->pushConditionalLevel(IfToken.getLocation(), /*wasskip*/false,
Expand Down
12 changes: 7 additions & 5 deletions clang/lib/Lex/PPExpressions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -593,11 +593,13 @@ static bool EvaluateDirectiveSubExpr(PPValue &LHS, unsigned MinPrec,
Token &PeekTok, bool ValueLive,
bool &IncludedUndefinedIds,
Preprocessor &PP) {
if (PP.getPreprocessorOpts().SingleFileParseMode && IncludedUndefinedIds) {
// The single-file parse mode behavior kicks in as soon as single identifier
// is undefined. If we've already seen one, there's no point in continuing
// with the rest of the expression. Besides saving work, this also prevents
// calling undefined function-like macros.
if ((PP.getPreprocessorOpts().SingleFileParseMode ||
PP.getPreprocessorOpts().SingleModuleParseMode) &&
IncludedUndefinedIds) {
// The single-{file,module}-parse mode behavior kicks in as soon as single
// identifier is undefined. If we've already seen one, there's no point in
// continuing with the rest of the expression. Besides saving work, this
// also prevents calling undefined function-like macros.
PP.DiscardUntilEndOfDirective(PeekTok);
return true;
}
Expand Down
92 changes: 92 additions & 0 deletions clang/test/Modules/single-module-parse-mode.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// RUN: rm -rf %t
// RUN: split-file %s %t

// RUN: %clang_cc1 -fmodules -fmodules-cache-path=%t/cache \
// RUN: -emit-module %t/module.modulemap -fmodule-name=B -o %t/cache/B.pcm \
// RUN: -fmodules-single-module-parse-mode 2>&1 | FileCheck %s

// Modules are not imported.
// CHECK-NOT: A.h:1:2: error: unreachable

// Headers belonging to this module are included.
// CHECK: B2.h:2:2: warning: success

// Non-modular headers are included.
// CHECK: T.h:2:2: warning: success

// No branches are entered for #if UNDEFINED.
// CHECK-NOT: B1.h:6:2: error: unreachable
// CHECK-NOT: B1.h:8:2: error: unreachable
// CHECK-NOT: B1.h:10:2: error: unreachable

// No branches are entered for #ifdef UNDEFINED.
// CHECK-NOT: B1.h:14:2: error: unreachable
// CHECK-NOT: B1.h:16:2: error: unreachable

// No branches are entered for #ifndef UNDEFINED.
// CHECK-NOT: B1.h:20:2: error: unreachable
// CHECK-NOT: B1.h:22:2: error: unreachable

// No error messages are emitted for UNDEFINED_FUNCTION_LIKE().
// CHECK-NOT: B1.h:25:2: error: unreachable

// The correct branch is entered for #if DEFINED.
// CHECK: B1.h:32:3: warning: success
// CHECK-NOT: B1.h:34:3: error: unreachable
// CHECK-NOT: B1.h:36:3: error: unreachable

// Headers belonging to this module are included.
// CHECK: B2.h:2:2: warning: success

//--- module.modulemap
module A { header "A.h" }
module B {
header "B1.h"
header "B2.h"
}
//--- A.h
#error unreachable
//--- B1.h
#include "A.h"
#include "B2.h"
#include "T.h"

#if UNDEFINED
# error unreachable
#elif UNDEFINED2
# error unreachable
#else
# error unreachable
#endif

#ifdef UNDEFINED
# error unreachable
#else
# error unreachable
#endif

#ifndef UNDEFINED
# error unreachable
#else
# error unreachable
#endif

#if UNDEFINED_FUNCTION_LIKE()
#endif

#define DEFINED_1 1
#define DEFINED_2 1

#if DEFINED_1
# warning success
#elif DEFINED_2
# error unreachable
#else
# error unreachable
#endif
//--- B2.h
// Headers belonging to this module are included.
#warning success
//--- T.h
// Non-modular headers are included.
#warning success
Loading