[flang][runtime] Let more list-directed child input advance (#160590)

Whether list-directed child READ statements should be allowed to advance
to further records is neither explicit in the standard nor consistent in
existing Fortran implementations. We allow child namelist READ
statements to advance, but not other list- directed child input.

This patch refines our interpretation of this case. Child namelist READ
statements continue to be able to advance; in addition, non-namelist
child READ statements can now advance if their parent READ statement is
a list-directed input statement at the top level, or a child that could.
But non-namelist list-directed child input taking place in a context
with explicit format control won't advance to following records, so that
the format-controlled parent READ statement can retain control over
record advancement.

Also corrects two cases of record repositioning in numeric input
editing, which were failing under child input because they weren't
allowing for left tab limits.

Fixes https://github.com/llvm/llvm-project/issues/160351.
This commit is contained in:
Peter Klausler
2025-09-30 10:35:06 -07:00
committed by GitHub
parent 2780c209e1
commit 673e3051b0
6 changed files with 65 additions and 19 deletions

View File

@@ -67,6 +67,17 @@ public:
RT_API_ATTRS int GetIoStat() const { return ioStat_; }
RT_API_ATTRS bool GetIoMsg(char *, std::size_t);
// Sets the HasEnd flag so that EOF isn't fatal; used to peek ahead
RT_API_ATTRS bool SetHasEnd(bool yes = true) {
bool oldValue{(flags_ & hasEnd) != 0};
if (yes) {
flags_ |= hasEnd;
} else {
flags_ &= ~hasEnd;
}
return oldValue;
}
private:
enum Flag : std::uint8_t {
hasIoStat = 1, // IOSTAT=

View File

@@ -703,6 +703,13 @@ public:
using ListDirectedStatementState<DIR>::GetNextDataEdit;
RT_API_ATTRS bool AdvanceRecord(int = 1);
RT_API_ATTRS int EndIoStatement();
RT_API_ATTRS bool CanAdvance() {
return DIR == Direction::Input &&
(canAdvance_ || this->mutableModes().inNamelist);
}
private:
bool canAdvance_{false};
};
template <Direction DIR>

View File

@@ -47,9 +47,11 @@ static RT_API_ATTRS common::optional<bool> DefinedFormattedIo(
const typeInfo::DerivedType &derived,
const typeInfo::SpecialBinding &special,
const SubscriptValue subscripts[]) {
// Look at the next data edit descriptor. If this is list-directed I/O, the
// "maxRepeat=0" argument will prevent the input from advancing over an
// Look at the next data edit descriptor. If this is list-directed input,
// the "maxRepeat=0" argument will prevent the input from advancing over an
// initial '(' that shouldn't be consumed now as the start of a real part.
// It also allows reaching EOF without crashing, since the EOF only matters
// if a child READ is actually performed.
common::optional<DataEdit> peek{io.GetNextDataEdit(/*maxRepeat=*/0)};
if (peek &&
(peek->descriptor == DataEdit::DefinedDerivedType ||

View File

@@ -53,11 +53,13 @@ static RT_API_ATTRS bool EditBOZInput(
IoStatementState &io, const DataEdit &edit, void *n, std::size_t bytes) {
// Skip leading white space & zeroes
common::optional<int> remaining{io.CueUpInput(edit)};
auto start{io.GetConnectionState().positionInRecord};
const ConnectionState &connection{io.GetConnectionState()};
auto leftTabLimit{connection.leftTabLimit.value_or(0)};
auto start{connection.positionInRecord - leftTabLimit};
common::optional<char32_t> next{io.NextInField(remaining, edit)};
if (next.value_or('?') == '0') {
do {
start = io.GetConnectionState().positionInRecord;
start = connection.positionInRecord - leftTabLimit;
next = io.NextInField(remaining, edit);
} while (next && *next == '0');
}
@@ -447,7 +449,9 @@ static RT_API_ATTRS ScannedRealInput ScanRealInput(
}
// In list-directed input, a bad exponent is not consumed.
auto nextBeforeExponent{next};
auto startExponent{io.GetConnectionState().positionInRecord};
const ConnectionState &connection{io.GetConnectionState()};
auto leftTabLimit{connection.leftTabLimit.value_or(0)};
auto startExponent{connection.positionInRecord - leftTabLimit};
bool hasGoodExponent{false};
if (next) {
if (isHexadecimal) {

View File

@@ -880,6 +880,9 @@ ListDirectedStatementState<Direction::Input>::GetNextDataEdit(
edit.descriptor = DataEdit::ListDirectedImaginaryPart;
}
auto fastField{io.GetUpcomingFastAsciiField()};
// Reaching EOF is okay when peeking at list-directed defined input;
// pretend that there's an END= in that case.
bool oldHasEnd{maxRepeat == 0 && !io.GetIoErrorHandler().SetHasEnd()};
auto ch{io.GetNextNonBlank(byteCount, &fastField)};
if (ch && *ch == comma && eatComma_) {
// Consume comma & whitespace after previous item.
@@ -890,19 +893,23 @@ ListDirectedStatementState<Direction::Input>::GetNextDataEdit(
ch = io.GetNextNonBlank(byteCount, &fastField);
}
eatComma_ = true;
if (!ch) {
return common::nullopt;
if (maxRepeat == 0 && !oldHasEnd) {
io.GetIoErrorHandler().SetHasEnd(false);
}
if (*ch == '/') {
if (!ch) { // EOF
if (maxRepeat == 0) {
return edit; // DataEdit::ListDirected for look-ahead
} else {
return common::nullopt;
}
} else if (*ch == '/') {
hitSlash_ = true;
edit.descriptor = DataEdit::ListDirectedNullValue;
return edit;
}
if (*ch == comma) { // separator: null value
} else if (*ch == comma) { // separator: null value
edit.descriptor = DataEdit::ListDirectedNullValue;
return edit;
}
if (imaginaryPart_) { // can't repeat components
} else if (imaginaryPart_) { // can't repeat components
return edit;
}
if (*ch >= '0' && *ch <= '9' && fastField.MightBeRepetitionCount()) {
@@ -1103,10 +1110,19 @@ ChildListIoStatementState<DIR>::ChildListIoStatementState(
: ChildIoStatementState<DIR>{child, sourceFile, sourceLine} {
#if !defined(RT_DEVICE_AVOID_RECURSION)
if constexpr (DIR == Direction::Input) {
if (auto *listInput{child.parent()
if (const auto *listInput{child.parent()
.get_if<ListDirectedStatementState<Direction::Input>>()}) {
this->set_eatComma(listInput->eatComma());
this->namelistGroup_ = listInput->namelistGroup();
if (auto *childListInput{child.parent()
.get_if<ChildListIoStatementState<Direction::Input>>()}) {
// Child list input whose parent is child list input: can advance
// if the parent can.
this->canAdvance_ = childListInput->CanAdvance();
} else {
// Child list input of top-level list input: can advance.
this->canAdvance_ = true;
}
}
}
#else
@@ -1117,12 +1133,7 @@ ChildListIoStatementState<DIR>::ChildListIoStatementState(
template <Direction DIR>
bool ChildListIoStatementState<DIR>::AdvanceRecord(int n) {
#if !defined(RT_DEVICE_AVOID_RECURSION)
// Allow child NAMELIST input to advance
if (DIR == Direction::Input && this->mutableModes().inNamelist) {
return this->child().parent().AdvanceRecord(n);
} else {
return false;
}
return this->CanAdvance() && this->child().parent().AdvanceRecord(n);
#else
this->ReportUnsupportedChildIo();
#endif

View File

@@ -928,6 +928,17 @@ print *, [(j,j=1,10)]
and the portable interpretation across the most common Fortran
compilers.
* `NAMELIST` child input statements are allowed to advance to further
input records.
Further, advancement is allowed when the parent input statement is
a non-child (top level) list-directed input statement, or, recursively,
an intermediate child list-directed input statement that can advance.
This means that non-`NAMELIST` list-directed child input statements are
not allowed to advance when they have an ancestor formatted input statement
that is not list-directed and there is no intervening `NAMELIST`.
This design allows format-driven input with `DT` editing to retain
control over advancement in child input, while otherwise allowing it.
## De Facto Standard Features
* `EXTENDS_TYPE_OF()` returns `.TRUE.` if both of its arguments have the