[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:
parent
f8fc883da9
commit
86bee81912
@ -257,6 +257,10 @@ public:
|
|||||||
provenanceMap_.Put(pm);
|
provenanceMap_.Put(pm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MarkPossibleFixedFormContinuation() {
|
||||||
|
possibleFixedFormContinuations_.push_back(BufferedBytes());
|
||||||
|
}
|
||||||
|
|
||||||
std::size_t BufferedBytes() const;
|
std::size_t BufferedBytes() const;
|
||||||
void Marshal(AllCookedSources &); // marshals text into one contiguous block
|
void Marshal(AllCookedSources &); // marshals text into one contiguous block
|
||||||
void CompileProvenanceRangeToOffsetMappings(AllSources &);
|
void CompileProvenanceRangeToOffsetMappings(AllSources &);
|
||||||
@ -269,6 +273,7 @@ private:
|
|||||||
std::string data_; // all of it, prescanned and preprocessed
|
std::string data_; // all of it, prescanned and preprocessed
|
||||||
OffsetToProvenanceMappings provenanceMap_;
|
OffsetToProvenanceMappings provenanceMap_;
|
||||||
ProvenanceRangeToOffsetMappings invertedMap_;
|
ProvenanceRangeToOffsetMappings invertedMap_;
|
||||||
|
std::list<std::size_t> possibleFixedFormContinuations_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class AllCookedSources {
|
class AllCookedSources {
|
||||||
|
@ -125,6 +125,7 @@ public:
|
|||||||
TokenSequence &ClipComment(const Prescanner &, bool skipFirst = false);
|
TokenSequence &ClipComment(const Prescanner &, bool skipFirst = false);
|
||||||
const TokenSequence &CheckBadFortranCharacters(
|
const TokenSequence &CheckBadFortranCharacters(
|
||||||
Messages &, const Prescanner &, bool allowAmpersand) const;
|
Messages &, const Prescanner &, bool allowAmpersand) const;
|
||||||
|
bool BadlyNestedParentheses() const;
|
||||||
const TokenSequence &CheckBadParentheses(Messages &) const;
|
const TokenSequence &CheckBadParentheses(Messages &) const;
|
||||||
void Emit(CookedSource &) const;
|
void Emit(CookedSource &) const;
|
||||||
llvm::raw_ostream &Dump(llvm::raw_ostream &) const;
|
llvm::raw_ostream &Dump(llvm::raw_ostream &) const;
|
||||||
|
@ -295,8 +295,13 @@ void Prescanner::CheckAndEmitLine(
|
|||||||
// Applications play shenanigans with line continuation before and
|
// Applications play shenanigans with line continuation before and
|
||||||
// after #include'd subprogram argument lists.
|
// after #include'd subprogram argument lists.
|
||||||
if (!isNestedInIncludeDirective_ && !omitNewline_ &&
|
if (!isNestedInIncludeDirective_ && !omitNewline_ &&
|
||||||
!afterIncludeDirective_) {
|
!afterIncludeDirective_ && tokens.BadlyNestedParentheses()) {
|
||||||
tokens.CheckBadParentheses(messages_);
|
if (inFixedForm_ && nextLine_ < limit_ &&
|
||||||
|
IsPreprocessorDirectiveLine(nextLine_)) {
|
||||||
|
// don't complain
|
||||||
|
} else {
|
||||||
|
tokens.CheckBadParentheses(messages_);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
tokens.Emit(cooked_);
|
tokens.Emit(cooked_);
|
||||||
if (omitNewline_) {
|
if (omitNewline_) {
|
||||||
@ -350,7 +355,16 @@ void Prescanner::LabelField(TokenSequence &token) {
|
|||||||
++column_;
|
++column_;
|
||||||
}
|
}
|
||||||
if (badColumn && !preprocessor_.IsNameDefined(token.CurrentOpenToken())) {
|
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),
|
Say(GetProvenance(start + *badColumn - 1),
|
||||||
*badColumn == 6
|
*badColumn == 6
|
||||||
? "Statement should not begin with a continuation line"_warn_en_US
|
? "Statement should not begin with a continuation line"_warn_en_US
|
||||||
|
@ -247,6 +247,8 @@ private:
|
|||||||
bool omitNewline_{false};
|
bool omitNewline_{false};
|
||||||
bool skipLeadingAmpersand_{false};
|
bool skipLeadingAmpersand_{false};
|
||||||
|
|
||||||
|
const std::size_t firstCookedCharacterOffset_{cooked_.BufferedBytes()};
|
||||||
|
|
||||||
const Provenance spaceProvenance_{
|
const Provenance spaceProvenance_{
|
||||||
allSources_.CompilerInsertionProvenance(' ')};
|
allSources_.CompilerInsertionProvenance(' ')};
|
||||||
const Provenance backslashProvenance_{
|
const Provenance backslashProvenance_{
|
||||||
|
@ -513,6 +513,16 @@ void CookedSource::Marshal(AllCookedSources &allCookedSources) {
|
|||||||
"(after end of source)"));
|
"(after end of source)"));
|
||||||
data_ = buffer_.Marshal();
|
data_ = buffer_.Marshal();
|
||||||
buffer_.clear();
|
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);
|
allCookedSources.Register(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -378,9 +378,7 @@ const TokenSequence &TokenSequence::CheckBadFortranCharacters(
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TokenSequence &TokenSequence::CheckBadParentheses(
|
bool TokenSequence::BadlyNestedParentheses() const {
|
||||||
Messages &messages) const {
|
|
||||||
// First, a quick pass with no allocation for the common case
|
|
||||||
int nesting{0};
|
int nesting{0};
|
||||||
std::size_t tokens{SizeInTokens()};
|
std::size_t tokens{SizeInTokens()};
|
||||||
for (std::size_t j{0}; j < tokens; ++j) {
|
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
|
// There's an error; diagnose it
|
||||||
|
std::size_t tokens{SizeInTokens()};
|
||||||
std::vector<std::size_t> stack;
|
std::vector<std::size_t> stack;
|
||||||
for (std::size_t j{0}; j < tokens; ++j) {
|
for (std::size_t j{0}; j < tokens; ++j) {
|
||||||
CharBlock token{TokenAt(j)};
|
CharBlock token{TokenAt(j)};
|
||||||
|
1
flang/test/Preprocessing/ff-args.h
Normal file
1
flang/test/Preprocessing/ff-args.h
Normal file
@ -0,0 +1 @@
|
|||||||
|
+3.14159)
|
14
flang/test/Preprocessing/ff-include-args.F
Normal file
14
flang/test/Preprocessing/ff-include-args.F
Normal 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
|
Loading…
x
Reference in New Issue
Block a user