[flang][preprocessor] Fixed-form continuation across preprocessing di… (#95332)

…rective

Implement fixed-form line continuation when the continuation line is the
result of text produced by an #include or other preprocessing directive.
This accommodates the somewhat common practice of putting dummy or
actual arguments into a header file and #including it into several code
sites.

Fixes https://github.com/llvm/llvm-project/issues/78928.
This commit is contained in:
Peter Klausler 2024-06-13 11:22:27 -07:00 committed by GitHub
parent f8fc883da9
commit 86bee81912
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 58 additions and 7 deletions

View File

@ -257,6 +257,10 @@ public:
provenanceMap_.Put(pm);
}
void MarkPossibleFixedFormContinuation() {
possibleFixedFormContinuations_.push_back(BufferedBytes());
}
std::size_t BufferedBytes() const;
void Marshal(AllCookedSources &); // marshals text into one contiguous block
void CompileProvenanceRangeToOffsetMappings(AllSources &);
@ -269,6 +273,7 @@ private:
std::string data_; // all of it, prescanned and preprocessed
OffsetToProvenanceMappings provenanceMap_;
ProvenanceRangeToOffsetMappings invertedMap_;
std::list<std::size_t> possibleFixedFormContinuations_;
};
class AllCookedSources {

View File

@ -125,6 +125,7 @@ public:
TokenSequence &ClipComment(const Prescanner &, bool skipFirst = false);
const TokenSequence &CheckBadFortranCharacters(
Messages &, const Prescanner &, bool allowAmpersand) const;
bool BadlyNestedParentheses() const;
const TokenSequence &CheckBadParentheses(Messages &) const;
void Emit(CookedSource &) const;
llvm::raw_ostream &Dump(llvm::raw_ostream &) const;

View File

@ -295,8 +295,13 @@ void Prescanner::CheckAndEmitLine(
// Applications play shenanigans with line continuation before and
// after #include'd subprogram argument lists.
if (!isNestedInIncludeDirective_ && !omitNewline_ &&
!afterIncludeDirective_) {
tokens.CheckBadParentheses(messages_);
!afterIncludeDirective_ && tokens.BadlyNestedParentheses()) {
if (inFixedForm_ && nextLine_ < limit_ &&
IsPreprocessorDirectiveLine(nextLine_)) {
// don't complain
} else {
tokens.CheckBadParentheses(messages_);
}
}
tokens.Emit(cooked_);
if (omitNewline_) {
@ -350,7 +355,16 @@ void Prescanner::LabelField(TokenSequence &token) {
++column_;
}
if (badColumn && !preprocessor_.IsNameDefined(token.CurrentOpenToken())) {
if (features_.ShouldWarn(common::UsageWarning::Scanning)) {
if (prescannerNesting_ > 0 && *badColumn == 6 &&
cooked_.BufferedBytes() == firstCookedCharacterOffset_) {
// This is the first source line in #included text or conditional
// code under #if.
// If it turns out that the preprocessed text begins with a
// fixed form continuation line, the newline at the end
// of the latest source line beforehand will be deleted in
// CookedSource::Marshal().
cooked_.MarkPossibleFixedFormContinuation();
} else if (features_.ShouldWarn(common::UsageWarning::Scanning)) {
Say(GetProvenance(start + *badColumn - 1),
*badColumn == 6
? "Statement should not begin with a continuation line"_warn_en_US

View File

@ -247,6 +247,8 @@ private:
bool omitNewline_{false};
bool skipLeadingAmpersand_{false};
const std::size_t firstCookedCharacterOffset_{cooked_.BufferedBytes()};
const Provenance spaceProvenance_{
allSources_.CompilerInsertionProvenance(' ')};
const Provenance backslashProvenance_{

View File

@ -513,6 +513,16 @@ void CookedSource::Marshal(AllCookedSources &allCookedSources) {
"(after end of source)"));
data_ = buffer_.Marshal();
buffer_.clear();
for (std::size_t ffStart : possibleFixedFormContinuations_) {
if (ffStart > 0 && ffStart + 1 < data_.size() &&
data_[ffStart - 1] == '\n' && data_[ffStart] == ' ') {
// This fixed form include line is the first source line in an
// #include file (or after an empty one). Connect it with the previous
// source line by deleting its terminal newline.
data_[ffStart - 1] = ' ';
}
}
possibleFixedFormContinuations_.clear();
allCookedSources.Register(*this);
}

View File

@ -378,9 +378,7 @@ const TokenSequence &TokenSequence::CheckBadFortranCharacters(
return *this;
}
const TokenSequence &TokenSequence::CheckBadParentheses(
Messages &messages) const {
// First, a quick pass with no allocation for the common case
bool TokenSequence::BadlyNestedParentheses() const {
int nesting{0};
std::size_t tokens{SizeInTokens()};
for (std::size_t j{0}; j < tokens; ++j) {
@ -394,8 +392,14 @@ const TokenSequence &TokenSequence::CheckBadParentheses(
}
}
}
if (nesting != 0) {
return nesting != 0;
}
const TokenSequence &TokenSequence::CheckBadParentheses(
Messages &messages) const {
if (BadlyNestedParentheses()) {
// There's an error; diagnose it
std::size_t tokens{SizeInTokens()};
std::vector<std::size_t> stack;
for (std::size_t j{0}; j < tokens; ++j) {
CharBlock token{TokenAt(j)};

View File

@ -0,0 +1 @@
+3.14159)

View File

@ -0,0 +1,14 @@
! RUN: %flang -E %s 2>&1 | FileCheck %s
! CHECK: call foo ( 3.14159)
! CHECK: subroutine foo(test)
call foo (
#include "ff-args.h"
end
#define TEST
subroutine foo(
#ifdef TEST
+test)
#else
+)
#endif
end