
check-io.cpp was missing checks for the definability of logical-valued specifiers in INQUIRE statements (e.g. EXIST=), and therefore also not noting the definitions of those variables. This could lead to bogus warnings about undefined function result variables, and also to missed errors about immutable objects appearing in those specifiers. Fixes https://github.com/llvm/llvm-project/issues/144453.
1255 lines
45 KiB
C++
1255 lines
45 KiB
C++
//===-- lib/Semantics/check-io.cpp ----------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "check-io.h"
|
|
#include "definable.h"
|
|
#include "flang/Common/format.h"
|
|
#include "flang/Common/indirection.h"
|
|
#include "flang/Evaluate/tools.h"
|
|
#include "flang/Parser/characters.h"
|
|
#include "flang/Parser/tools.h"
|
|
#include "flang/Semantics/expression.h"
|
|
#include "flang/Semantics/tools.h"
|
|
#include <unordered_map>
|
|
|
|
namespace Fortran::semantics {
|
|
|
|
// TODO: C1234, C1235 -- defined I/O constraints
|
|
|
|
class FormatErrorReporter {
|
|
public:
|
|
FormatErrorReporter(SemanticsContext &context,
|
|
const parser::CharBlock &formatCharBlock, int errorAllowance = 3)
|
|
: context_{context}, formatCharBlock_{formatCharBlock},
|
|
errorAllowance_{errorAllowance} {}
|
|
|
|
bool Say(const common::FormatMessage &);
|
|
|
|
private:
|
|
SemanticsContext &context_;
|
|
const parser::CharBlock &formatCharBlock_;
|
|
int errorAllowance_; // initialized to maximum number of errors to report
|
|
};
|
|
|
|
bool FormatErrorReporter::Say(const common::FormatMessage &msg) {
|
|
if (!msg.isError &&
|
|
!context_.ShouldWarn(common::LanguageFeature::AdditionalFormats)) {
|
|
return false;
|
|
}
|
|
parser::MessageFormattedText text{
|
|
parser::MessageFixedText{msg.text, strlen(msg.text),
|
|
msg.isError ? parser::Severity::Error : parser::Severity::Warning},
|
|
msg.arg};
|
|
if (formatCharBlock_.size()) {
|
|
// The input format is a folded expression. Error markers span the full
|
|
// original unfolded expression in formatCharBlock_.
|
|
context_.Say(formatCharBlock_, text);
|
|
} else {
|
|
// The input format is a source expression. Error markers have an offset
|
|
// and length relative to the beginning of formatCharBlock_.
|
|
parser::CharBlock messageCharBlock{
|
|
parser::CharBlock(formatCharBlock_.begin() + msg.offset, msg.length)};
|
|
context_.Say(messageCharBlock, text);
|
|
}
|
|
return msg.isError && --errorAllowance_ <= 0;
|
|
}
|
|
|
|
void IoChecker::Enter(
|
|
const parser::Statement<common::Indirection<parser::FormatStmt>> &stmt) {
|
|
if (!stmt.label) {
|
|
context_.Say("Format statement must be labeled"_err_en_US); // C1301
|
|
}
|
|
const char *formatStart{static_cast<const char *>(
|
|
std::memchr(stmt.source.begin(), '(', stmt.source.size()))};
|
|
parser::CharBlock reporterCharBlock{formatStart, static_cast<std::size_t>(0)};
|
|
FormatErrorReporter reporter{context_, reporterCharBlock};
|
|
auto reporterWrapper{[&](const auto &msg) { return reporter.Say(msg); }};
|
|
switch (context_.GetDefaultKind(TypeCategory::Character)) {
|
|
case 1: {
|
|
common::FormatValidator<char> validator{formatStart,
|
|
stmt.source.size() - (formatStart - stmt.source.begin()),
|
|
reporterWrapper};
|
|
validator.Check();
|
|
break;
|
|
}
|
|
case 2: { // TODO: Get this to work.
|
|
common::FormatValidator<char16_t> validator{
|
|
/*???*/ nullptr, /*???*/ 0, reporterWrapper};
|
|
validator.Check();
|
|
break;
|
|
}
|
|
case 4: { // TODO: Get this to work.
|
|
common::FormatValidator<char32_t> validator{
|
|
/*???*/ nullptr, /*???*/ 0, reporterWrapper};
|
|
validator.Check();
|
|
break;
|
|
}
|
|
default:
|
|
CRASH_NO_CASE;
|
|
}
|
|
}
|
|
|
|
void IoChecker::Enter(const parser::ConnectSpec &spec) {
|
|
// ConnectSpec context FileNameExpr
|
|
if (std::get_if<parser::FileNameExpr>(&spec.u)) {
|
|
SetSpecifier(IoSpecKind::File);
|
|
}
|
|
}
|
|
|
|
// Ignore trailing spaces (12.5.6.2 p1) and convert to upper case
|
|
static std::string Normalize(const std::string &value) {
|
|
auto upper{parser::ToUpperCaseLetters(value)};
|
|
std::size_t lastNonBlank{upper.find_last_not_of(' ')};
|
|
upper.resize(lastNonBlank == std::string::npos ? 0 : lastNonBlank + 1);
|
|
return upper;
|
|
}
|
|
|
|
void IoChecker::Enter(const parser::ConnectSpec::CharExpr &spec) {
|
|
IoSpecKind specKind{};
|
|
using ParseKind = parser::ConnectSpec::CharExpr::Kind;
|
|
switch (std::get<ParseKind>(spec.t)) {
|
|
case ParseKind::Access:
|
|
specKind = IoSpecKind::Access;
|
|
break;
|
|
case ParseKind::Action:
|
|
specKind = IoSpecKind::Action;
|
|
break;
|
|
case ParseKind::Asynchronous:
|
|
specKind = IoSpecKind::Asynchronous;
|
|
break;
|
|
case ParseKind::Blank:
|
|
specKind = IoSpecKind::Blank;
|
|
break;
|
|
case ParseKind::Decimal:
|
|
specKind = IoSpecKind::Decimal;
|
|
break;
|
|
case ParseKind::Delim:
|
|
specKind = IoSpecKind::Delim;
|
|
break;
|
|
case ParseKind::Encoding:
|
|
specKind = IoSpecKind::Encoding;
|
|
break;
|
|
case ParseKind::Form:
|
|
specKind = IoSpecKind::Form;
|
|
break;
|
|
case ParseKind::Pad:
|
|
specKind = IoSpecKind::Pad;
|
|
break;
|
|
case ParseKind::Position:
|
|
specKind = IoSpecKind::Position;
|
|
break;
|
|
case ParseKind::Round:
|
|
specKind = IoSpecKind::Round;
|
|
break;
|
|
case ParseKind::Sign:
|
|
specKind = IoSpecKind::Sign;
|
|
break;
|
|
case ParseKind::Carriagecontrol:
|
|
specKind = IoSpecKind::Carriagecontrol;
|
|
break;
|
|
case ParseKind::Convert:
|
|
specKind = IoSpecKind::Convert;
|
|
break;
|
|
case ParseKind::Dispose:
|
|
specKind = IoSpecKind::Dispose;
|
|
break;
|
|
}
|
|
SetSpecifier(specKind);
|
|
if (const std::optional<std::string> charConst{GetConstExpr<std::string>(
|
|
std::get<parser::ScalarDefaultCharExpr>(spec.t))}) {
|
|
std::string s{Normalize(*charConst)};
|
|
if (specKind == IoSpecKind::Access) {
|
|
flags_.set(Flag::KnownAccess);
|
|
flags_.set(Flag::AccessDirect, s == "DIRECT");
|
|
flags_.set(Flag::AccessStream, s == "STREAM");
|
|
}
|
|
CheckStringValue(specKind, *charConst, parser::FindSourceLocation(spec));
|
|
if (specKind == IoSpecKind::Carriagecontrol &&
|
|
(s == "FORTRAN" || s == "NONE")) {
|
|
context_.Say(parser::FindSourceLocation(spec),
|
|
"Unimplemented %s value '%s'"_err_en_US,
|
|
parser::ToUpperCaseLetters(common::EnumToString(specKind)),
|
|
*charConst);
|
|
}
|
|
}
|
|
}
|
|
|
|
void IoChecker::Enter(const parser::ConnectSpec::Newunit &var) {
|
|
CheckForDefinableVariable(var, "NEWUNIT");
|
|
SetSpecifier(IoSpecKind::Newunit);
|
|
}
|
|
|
|
void IoChecker::Enter(const parser::ConnectSpec::Recl &spec) {
|
|
SetSpecifier(IoSpecKind::Recl);
|
|
if (const std::optional<std::int64_t> recl{
|
|
GetConstExpr<std::int64_t>(spec)}) {
|
|
if (*recl <= 0) {
|
|
context_.Say(parser::FindSourceLocation(spec),
|
|
"RECL value (%jd) must be positive"_err_en_US,
|
|
*recl); // 12.5.6.15
|
|
}
|
|
}
|
|
}
|
|
|
|
void IoChecker::Enter(const parser::EndLabel &) {
|
|
SetSpecifier(IoSpecKind::End);
|
|
}
|
|
|
|
void IoChecker::Enter(const parser::EorLabel &) {
|
|
SetSpecifier(IoSpecKind::Eor);
|
|
}
|
|
|
|
void IoChecker::Enter(const parser::ErrLabel &) {
|
|
SetSpecifier(IoSpecKind::Err);
|
|
}
|
|
|
|
void IoChecker::Enter(const parser::FileUnitNumber &) {
|
|
SetSpecifier(IoSpecKind::Unit);
|
|
flags_.set(Flag::NumberUnit);
|
|
}
|
|
|
|
void IoChecker::Enter(const parser::Format &spec) {
|
|
SetSpecifier(IoSpecKind::Fmt);
|
|
flags_.set(Flag::FmtOrNml);
|
|
common::visit(
|
|
common::visitors{
|
|
[&](const parser::Label &) { flags_.set(Flag::LabelFmt); },
|
|
[&](const parser::Star &) { flags_.set(Flag::StarFmt); },
|
|
[&](const parser::Expr &format) {
|
|
const SomeExpr *expr{GetExpr(context_, format)};
|
|
if (!expr) {
|
|
return;
|
|
}
|
|
auto type{expr->GetType()};
|
|
if (type && type->category() == TypeCategory::Integer &&
|
|
type->kind() ==
|
|
context_.defaultKinds().GetDefaultKind(type->category()) &&
|
|
expr->Rank() == 0) {
|
|
flags_.set(Flag::AssignFmt);
|
|
if (!IsVariable(*expr)) {
|
|
context_.Say(format.source,
|
|
"Assigned format label must be a scalar variable"_err_en_US);
|
|
} else {
|
|
context_.Warn(common::LanguageFeature::Assign, format.source,
|
|
"Assigned format labels are deprecated"_port_en_US);
|
|
}
|
|
return;
|
|
}
|
|
if (type && type->category() != TypeCategory::Character &&
|
|
(type->category() != TypeCategory::Integer ||
|
|
expr->Rank() > 0) &&
|
|
context_.IsEnabled(
|
|
common::LanguageFeature::NonCharacterFormat)) {
|
|
// Legacy extension: using non-character variables, typically
|
|
// DATA-initialized with Hollerith, as format expressions.
|
|
context_.Warn(common::LanguageFeature::NonCharacterFormat,
|
|
format.source,
|
|
"Non-character format expression is not standard"_port_en_US);
|
|
} else if (!type ||
|
|
type->kind() !=
|
|
context_.defaultKinds().GetDefaultKind(type->category())) {
|
|
context_.Say(format.source,
|
|
"Format expression must be default character or default scalar integer"_err_en_US);
|
|
return;
|
|
}
|
|
flags_.set(Flag::CharFmt);
|
|
const std::optional<std::string> constantFormat{
|
|
GetConstExpr<std::string>(format)};
|
|
if (!constantFormat) {
|
|
return;
|
|
}
|
|
// validate constant format -- 12.6.2.2
|
|
bool isFolded{constantFormat->size() != format.source.size() - 2};
|
|
parser::CharBlock reporterCharBlock{isFolded
|
|
? parser::CharBlock{format.source}
|
|
: parser::CharBlock{format.source.begin() + 1,
|
|
static_cast<std::size_t>(0)}};
|
|
FormatErrorReporter reporter{context_, reporterCharBlock};
|
|
auto reporterWrapper{
|
|
[&](const auto &msg) { return reporter.Say(msg); }};
|
|
switch (context_.GetDefaultKind(TypeCategory::Character)) {
|
|
case 1: {
|
|
common::FormatValidator<char> validator{constantFormat->c_str(),
|
|
constantFormat->length(), reporterWrapper, stmt_};
|
|
validator.Check();
|
|
break;
|
|
}
|
|
case 2: {
|
|
// TODO: Get this to work. (Maybe combine with earlier instance?)
|
|
common::FormatValidator<char16_t> validator{
|
|
/*???*/ nullptr, /*???*/ 0, reporterWrapper, stmt_};
|
|
validator.Check();
|
|
break;
|
|
}
|
|
case 4: {
|
|
// TODO: Get this to work. (Maybe combine with earlier instance?)
|
|
common::FormatValidator<char32_t> validator{
|
|
/*???*/ nullptr, /*???*/ 0, reporterWrapper, stmt_};
|
|
validator.Check();
|
|
break;
|
|
}
|
|
default:
|
|
CRASH_NO_CASE;
|
|
}
|
|
},
|
|
},
|
|
spec.u);
|
|
}
|
|
|
|
void IoChecker::Enter(const parser::IdExpr &) { SetSpecifier(IoSpecKind::Id); }
|
|
|
|
void IoChecker::Enter(const parser::IdVariable &spec) {
|
|
SetSpecifier(IoSpecKind::Id);
|
|
const auto *expr{GetExpr(context_, spec)};
|
|
if (!expr || !expr->GetType()) {
|
|
return;
|
|
}
|
|
CheckForDefinableVariable(spec, "ID");
|
|
int kind{expr->GetType()->kind()};
|
|
int defaultKind{context_.GetDefaultKind(TypeCategory::Integer)};
|
|
if (kind < defaultKind) {
|
|
context_.Say(
|
|
"ID kind (%d) is smaller than default INTEGER kind (%d)"_err_en_US,
|
|
std::move(kind), std::move(defaultKind)); // C1229
|
|
}
|
|
}
|
|
|
|
void IoChecker::Enter(const parser::InputItem &spec) {
|
|
flags_.set(Flag::DataList);
|
|
const parser::Variable *var{std::get_if<parser::Variable>(&spec.u)};
|
|
if (!var) {
|
|
return;
|
|
}
|
|
CheckForDefinableVariable(*var, "Input");
|
|
if (auto expr{AnalyzeExpr(context_, *var)}) {
|
|
CheckForBadIoType(*expr,
|
|
flags_.test(Flag::FmtOrNml) ? common::DefinedIo::ReadFormatted
|
|
: common::DefinedIo::ReadUnformatted,
|
|
var->GetSource());
|
|
}
|
|
}
|
|
|
|
void IoChecker::Enter(const parser::InquireSpec &spec) {
|
|
// InquireSpec context FileNameExpr
|
|
if (std::get_if<parser::FileNameExpr>(&spec.u)) {
|
|
SetSpecifier(IoSpecKind::File);
|
|
}
|
|
}
|
|
|
|
void IoChecker::Enter(const parser::InquireSpec::CharVar &spec) {
|
|
IoSpecKind specKind{};
|
|
using ParseKind = parser::InquireSpec::CharVar::Kind;
|
|
switch (std::get<ParseKind>(spec.t)) {
|
|
case ParseKind::Access:
|
|
specKind = IoSpecKind::Access;
|
|
break;
|
|
case ParseKind::Action:
|
|
specKind = IoSpecKind::Action;
|
|
break;
|
|
case ParseKind::Asynchronous:
|
|
specKind = IoSpecKind::Asynchronous;
|
|
break;
|
|
case ParseKind::Blank:
|
|
specKind = IoSpecKind::Blank;
|
|
break;
|
|
case ParseKind::Decimal:
|
|
specKind = IoSpecKind::Decimal;
|
|
break;
|
|
case ParseKind::Delim:
|
|
specKind = IoSpecKind::Delim;
|
|
break;
|
|
case ParseKind::Direct:
|
|
specKind = IoSpecKind::Direct;
|
|
break;
|
|
case ParseKind::Encoding:
|
|
specKind = IoSpecKind::Encoding;
|
|
break;
|
|
case ParseKind::Form:
|
|
specKind = IoSpecKind::Form;
|
|
break;
|
|
case ParseKind::Formatted:
|
|
specKind = IoSpecKind::Formatted;
|
|
break;
|
|
case ParseKind::Iomsg:
|
|
specKind = IoSpecKind::Iomsg;
|
|
break;
|
|
case ParseKind::Name:
|
|
specKind = IoSpecKind::Name;
|
|
break;
|
|
case ParseKind::Pad:
|
|
specKind = IoSpecKind::Pad;
|
|
break;
|
|
case ParseKind::Position:
|
|
specKind = IoSpecKind::Position;
|
|
break;
|
|
case ParseKind::Read:
|
|
specKind = IoSpecKind::Read;
|
|
break;
|
|
case ParseKind::Readwrite:
|
|
specKind = IoSpecKind::Readwrite;
|
|
break;
|
|
case ParseKind::Round:
|
|
specKind = IoSpecKind::Round;
|
|
break;
|
|
case ParseKind::Sequential:
|
|
specKind = IoSpecKind::Sequential;
|
|
break;
|
|
case ParseKind::Sign:
|
|
specKind = IoSpecKind::Sign;
|
|
break;
|
|
case ParseKind::Status:
|
|
specKind = IoSpecKind::Status;
|
|
break;
|
|
case ParseKind::Stream:
|
|
specKind = IoSpecKind::Stream;
|
|
break;
|
|
case ParseKind::Unformatted:
|
|
specKind = IoSpecKind::Unformatted;
|
|
break;
|
|
case ParseKind::Write:
|
|
specKind = IoSpecKind::Write;
|
|
break;
|
|
case ParseKind::Carriagecontrol:
|
|
specKind = IoSpecKind::Carriagecontrol;
|
|
break;
|
|
case ParseKind::Convert:
|
|
specKind = IoSpecKind::Convert;
|
|
break;
|
|
case ParseKind::Dispose:
|
|
specKind = IoSpecKind::Dispose;
|
|
break;
|
|
}
|
|
const parser::Variable &var{
|
|
std::get<parser::ScalarDefaultCharVariable>(spec.t).thing.thing};
|
|
std::string what{parser::ToUpperCaseLetters(common::EnumToString(specKind))};
|
|
CheckForDefinableVariable(var, what);
|
|
WarnOnDeferredLengthCharacterScalar(
|
|
context_, GetExpr(context_, var), var.GetSource(), what.c_str());
|
|
SetSpecifier(specKind);
|
|
}
|
|
|
|
void IoChecker::Enter(const parser::InquireSpec::IntVar &spec) {
|
|
IoSpecKind specKind{};
|
|
using ParseKind = parser::InquireSpec::IntVar::Kind;
|
|
switch (std::get<parser::InquireSpec::IntVar::Kind>(spec.t)) {
|
|
case ParseKind::Iostat:
|
|
specKind = IoSpecKind::Iostat;
|
|
break;
|
|
case ParseKind::Nextrec:
|
|
specKind = IoSpecKind::Nextrec;
|
|
break;
|
|
case ParseKind::Number:
|
|
specKind = IoSpecKind::Number;
|
|
break;
|
|
case ParseKind::Pos:
|
|
specKind = IoSpecKind::Pos;
|
|
break;
|
|
case ParseKind::Recl:
|
|
specKind = IoSpecKind::Recl;
|
|
break;
|
|
case ParseKind::Size:
|
|
specKind = IoSpecKind::Size;
|
|
break;
|
|
}
|
|
CheckForDefinableVariable(std::get<parser::ScalarIntVariable>(spec.t),
|
|
parser::ToUpperCaseLetters(common::EnumToString(specKind)));
|
|
SetSpecifier(specKind);
|
|
}
|
|
|
|
void IoChecker::Enter(const parser::InquireSpec::LogVar &spec) {
|
|
IoSpecKind specKind{};
|
|
using ParseKind = parser::InquireSpec::LogVar::Kind;
|
|
switch (std::get<parser::InquireSpec::LogVar::Kind>(spec.t)) {
|
|
case ParseKind::Exist:
|
|
specKind = IoSpecKind::Exist;
|
|
break;
|
|
case ParseKind::Named:
|
|
specKind = IoSpecKind::Named;
|
|
break;
|
|
case ParseKind::Opened:
|
|
specKind = IoSpecKind::Opened;
|
|
break;
|
|
case ParseKind::Pending:
|
|
specKind = IoSpecKind::Pending;
|
|
break;
|
|
}
|
|
CheckForDefinableVariable(std::get<parser::ScalarLogicalVariable>(spec.t),
|
|
parser::ToUpperCaseLetters(common::EnumToString(specKind)));
|
|
SetSpecifier(specKind);
|
|
}
|
|
|
|
void IoChecker::Enter(const parser::IoControlSpec &spec) {
|
|
// IoControlSpec context Name
|
|
flags_.set(Flag::IoControlList);
|
|
if (std::holds_alternative<parser::Name>(spec.u)) {
|
|
SetSpecifier(IoSpecKind::Nml);
|
|
flags_.set(Flag::FmtOrNml);
|
|
}
|
|
}
|
|
|
|
void IoChecker::Enter(const parser::IoControlSpec::Asynchronous &spec) {
|
|
SetSpecifier(IoSpecKind::Asynchronous);
|
|
if (const std::optional<std::string> charConst{
|
|
GetConstExpr<std::string>(spec)}) {
|
|
flags_.set(Flag::AsynchronousYes, Normalize(*charConst) == "YES");
|
|
CheckStringValue(IoSpecKind::Asynchronous, *charConst,
|
|
parser::FindSourceLocation(spec)); // C1223
|
|
}
|
|
}
|
|
|
|
void IoChecker::Enter(const parser::IoControlSpec::CharExpr &spec) {
|
|
IoSpecKind specKind{};
|
|
using ParseKind = parser::IoControlSpec::CharExpr::Kind;
|
|
switch (std::get<ParseKind>(spec.t)) {
|
|
case ParseKind::Advance:
|
|
specKind = IoSpecKind::Advance;
|
|
break;
|
|
case ParseKind::Blank:
|
|
specKind = IoSpecKind::Blank;
|
|
break;
|
|
case ParseKind::Decimal:
|
|
specKind = IoSpecKind::Decimal;
|
|
break;
|
|
case ParseKind::Delim:
|
|
specKind = IoSpecKind::Delim;
|
|
break;
|
|
case ParseKind::Pad:
|
|
specKind = IoSpecKind::Pad;
|
|
break;
|
|
case ParseKind::Round:
|
|
specKind = IoSpecKind::Round;
|
|
break;
|
|
case ParseKind::Sign:
|
|
specKind = IoSpecKind::Sign;
|
|
break;
|
|
}
|
|
SetSpecifier(specKind);
|
|
if (const std::optional<std::string> charConst{GetConstExpr<std::string>(
|
|
std::get<parser::ScalarDefaultCharExpr>(spec.t))}) {
|
|
if (specKind == IoSpecKind::Advance) {
|
|
flags_.set(Flag::AdvanceYes, Normalize(*charConst) == "YES");
|
|
}
|
|
CheckStringValue(specKind, *charConst, parser::FindSourceLocation(spec));
|
|
}
|
|
}
|
|
|
|
void IoChecker::Enter(const parser::IoControlSpec::Pos &) {
|
|
SetSpecifier(IoSpecKind::Pos);
|
|
}
|
|
|
|
void IoChecker::Enter(const parser::IoControlSpec::Rec &) {
|
|
SetSpecifier(IoSpecKind::Rec);
|
|
}
|
|
|
|
void IoChecker::Enter(const parser::IoControlSpec::Size &var) {
|
|
CheckForDefinableVariable(var, "SIZE");
|
|
SetSpecifier(IoSpecKind::Size);
|
|
}
|
|
|
|
void IoChecker::Enter(const parser::IoUnit &spec) {
|
|
if (const parser::Variable * var{std::get_if<parser::Variable>(&spec.u)}) {
|
|
// Only now after generic resolution can it be known whether a function
|
|
// call appearing as UNIT=f() is an integer scalar external unit number
|
|
// or a character pointer for internal I/O.
|
|
const auto *expr{GetExpr(context_, *var)};
|
|
std::optional<evaluate::DynamicType> dyType;
|
|
if (expr) {
|
|
dyType = expr->GetType();
|
|
}
|
|
if (dyType && dyType->category() == TypeCategory::Integer) {
|
|
if (expr->Rank() != 0) {
|
|
context_.Say(parser::FindSourceLocation(*var),
|
|
"I/O unit number must be scalar"_err_en_US);
|
|
}
|
|
// In the case of an integer unit number variable, rewrite the parse
|
|
// tree as if the unit had been parsed as a FileUnitNumber in order
|
|
// to ease lowering.
|
|
auto &mutableSpec{const_cast<parser::IoUnit &>(spec)};
|
|
auto &mutableVar{std::get<parser::Variable>(mutableSpec.u)};
|
|
auto source{mutableVar.GetSource()};
|
|
auto typedExpr{std::move(mutableVar.typedExpr)};
|
|
auto newExpr{common::visit(
|
|
[](auto &&indirection) {
|
|
return parser::Expr{std::move(indirection)};
|
|
},
|
|
std::move(mutableVar.u))};
|
|
newExpr.source = source;
|
|
newExpr.typedExpr = std::move(typedExpr);
|
|
mutableSpec.u = common::Indirection<parser::Expr>{std::move(newExpr)};
|
|
SetSpecifier(IoSpecKind::Unit);
|
|
flags_.set(Flag::NumberUnit);
|
|
} else if (!dyType || dyType->category() != TypeCategory::Character) {
|
|
SetSpecifier(IoSpecKind::Unit);
|
|
context_.Say(parser::FindSourceLocation(*var),
|
|
"I/O unit must be a character variable or a scalar integer expression"_err_en_US);
|
|
} else { // CHARACTER variable (internal I/O)
|
|
if (stmt_ == IoStmtKind::Write) {
|
|
CheckForDefinableVariable(*var, "Internal file");
|
|
WarnOnDeferredLengthCharacterScalar(
|
|
context_, expr, var->GetSource(), "Internal file");
|
|
}
|
|
if (HasVectorSubscript(*expr)) {
|
|
context_.Say(parser::FindSourceLocation(*var), // C1201
|
|
"Internal file must not have a vector subscript"_err_en_US);
|
|
}
|
|
SetSpecifier(IoSpecKind::Unit);
|
|
flags_.set(Flag::InternalUnit);
|
|
}
|
|
} else if (std::get_if<parser::Star>(&spec.u)) {
|
|
SetSpecifier(IoSpecKind::Unit);
|
|
flags_.set(Flag::StarUnit);
|
|
} else if (const common::Indirection<parser::Expr> *pexpr{
|
|
std::get_if<common::Indirection<parser::Expr>>(&spec.u)}) {
|
|
const auto *expr{GetExpr(context_, *pexpr)};
|
|
std::optional<evaluate::DynamicType> dyType;
|
|
if (expr) {
|
|
dyType = expr->GetType();
|
|
}
|
|
if (!expr || !dyType) {
|
|
context_.Say(parser::FindSourceLocation(*pexpr),
|
|
"I/O unit must be a character variable or scalar integer expression"_err_en_US);
|
|
} else if (dyType->category() != TypeCategory::Integer) {
|
|
context_.Say(parser::FindSourceLocation(*pexpr),
|
|
"I/O unit must be a character variable or a scalar integer expression, but is an expression of type %s"_err_en_US,
|
|
parser::ToUpperCaseLetters(dyType->AsFortran()));
|
|
} else if (expr->Rank() != 0) {
|
|
context_.Say(parser::FindSourceLocation(*pexpr),
|
|
"I/O unit number must be scalar"_err_en_US);
|
|
}
|
|
SetSpecifier(IoSpecKind::Unit);
|
|
flags_.set(Flag::NumberUnit);
|
|
}
|
|
}
|
|
|
|
void IoChecker::Enter(const parser::MsgVariable &msgVar) {
|
|
const parser::Variable &var{msgVar.v.thing.thing};
|
|
if (stmt_ == IoStmtKind::None) {
|
|
// allocate, deallocate, image control
|
|
CheckForDefinableVariable(var, "ERRMSG");
|
|
WarnOnDeferredLengthCharacterScalar(
|
|
context_, GetExpr(context_, var), var.GetSource(), "ERRMSG=");
|
|
} else {
|
|
CheckForDefinableVariable(var, "IOMSG");
|
|
WarnOnDeferredLengthCharacterScalar(
|
|
context_, GetExpr(context_, var), var.GetSource(), "IOMSG=");
|
|
SetSpecifier(IoSpecKind::Iomsg);
|
|
}
|
|
}
|
|
|
|
void IoChecker::Enter(const parser::OutputItem &item) {
|
|
flags_.set(Flag::DataList);
|
|
if (const auto *x{std::get_if<parser::Expr>(&item.u)}) {
|
|
if (const auto *expr{GetExpr(context_, *x)}) {
|
|
if (evaluate::IsBOZLiteral(*expr)) {
|
|
context_.Say(parser::FindSourceLocation(*x), // C7109
|
|
"Output item must not be a BOZ literal constant"_err_en_US);
|
|
} else if (IsProcedure(*expr)) {
|
|
context_.Say(parser::FindSourceLocation(*x),
|
|
"Output item must not be a procedure"_err_en_US); // C1233
|
|
}
|
|
CheckForBadIoType(*expr,
|
|
flags_.test(Flag::FmtOrNml) ? common::DefinedIo::WriteFormatted
|
|
: common::DefinedIo::WriteUnformatted,
|
|
parser::FindSourceLocation(item));
|
|
}
|
|
}
|
|
}
|
|
|
|
void IoChecker::Enter(const parser::StatusExpr &spec) {
|
|
SetSpecifier(IoSpecKind::Status);
|
|
if (const std::optional<std::string> charConst{
|
|
GetConstExpr<std::string>(spec)}) {
|
|
// Status values for Open and Close are different.
|
|
std::string s{Normalize(*charConst)};
|
|
if (stmt_ == IoStmtKind::Open) {
|
|
flags_.set(Flag::KnownStatus);
|
|
flags_.set(Flag::StatusNew, s == "NEW");
|
|
flags_.set(Flag::StatusReplace, s == "REPLACE");
|
|
flags_.set(Flag::StatusScratch, s == "SCRATCH");
|
|
// CheckStringValue compares for OPEN Status string values.
|
|
CheckStringValue(
|
|
IoSpecKind::Status, *charConst, parser::FindSourceLocation(spec));
|
|
return;
|
|
}
|
|
CHECK(stmt_ == IoStmtKind::Close);
|
|
if (s != "DELETE" && s != "KEEP") {
|
|
context_.Say(parser::FindSourceLocation(spec),
|
|
"Invalid STATUS value '%s'"_err_en_US, *charConst);
|
|
}
|
|
}
|
|
}
|
|
|
|
void IoChecker::Enter(const parser::StatVariable &var) {
|
|
if (stmt_ == IoStmtKind::None) {
|
|
// allocate, deallocate, image control
|
|
CheckForDefinableVariable(var, "STAT");
|
|
} else {
|
|
CheckForDefinableVariable(var, "IOSTAT");
|
|
SetSpecifier(IoSpecKind::Iostat);
|
|
}
|
|
}
|
|
|
|
void IoChecker::Leave(const parser::BackspaceStmt &) {
|
|
CheckForPureSubprogram();
|
|
CheckForRequiredSpecifier(
|
|
flags_.test(Flag::NumberUnit), "UNIT number"); // C1240
|
|
CheckForUselessIomsg();
|
|
Done();
|
|
}
|
|
|
|
void IoChecker::Leave(const parser::CloseStmt &) {
|
|
CheckForPureSubprogram();
|
|
CheckForRequiredSpecifier(
|
|
flags_.test(Flag::NumberUnit), "UNIT number"); // C1208
|
|
CheckForUselessIomsg();
|
|
Done();
|
|
}
|
|
|
|
void IoChecker::Leave(const parser::EndfileStmt &) {
|
|
CheckForPureSubprogram();
|
|
CheckForRequiredSpecifier(
|
|
flags_.test(Flag::NumberUnit), "UNIT number"); // C1240
|
|
CheckForUselessIomsg();
|
|
Done();
|
|
}
|
|
|
|
void IoChecker::Leave(const parser::FlushStmt &) {
|
|
CheckForPureSubprogram();
|
|
CheckForRequiredSpecifier(
|
|
flags_.test(Flag::NumberUnit), "UNIT number"); // C1243
|
|
CheckForUselessIomsg();
|
|
Done();
|
|
}
|
|
|
|
void IoChecker::Leave(const parser::InquireStmt &stmt) {
|
|
if (std::get_if<std::list<parser::InquireSpec>>(&stmt.u)) {
|
|
CheckForPureSubprogram();
|
|
// Inquire by unit or by file (vs. by output list).
|
|
CheckForRequiredSpecifier(
|
|
flags_.test(Flag::NumberUnit) || specifierSet_.test(IoSpecKind::File),
|
|
"UNIT number or FILE"); // C1246
|
|
CheckForProhibitedSpecifier(IoSpecKind::File, IoSpecKind::Unit); // C1246
|
|
CheckForRequiredSpecifier(IoSpecKind::Id, IoSpecKind::Pending); // C1248
|
|
CheckForUselessIomsg();
|
|
}
|
|
Done();
|
|
}
|
|
|
|
void IoChecker::Leave(const parser::OpenStmt &) {
|
|
CheckForPureSubprogram();
|
|
CheckForRequiredSpecifier(specifierSet_.test(IoSpecKind::Unit) ||
|
|
specifierSet_.test(IoSpecKind::Newunit),
|
|
"UNIT or NEWUNIT"); // C1204, C1205
|
|
CheckForProhibitedSpecifier(
|
|
IoSpecKind::Newunit, IoSpecKind::Unit); // C1204, C1205
|
|
CheckForRequiredSpecifier(flags_.test(Flag::StatusNew), "STATUS='NEW'",
|
|
IoSpecKind::File); // 12.5.6.10
|
|
CheckForRequiredSpecifier(flags_.test(Flag::StatusReplace),
|
|
"STATUS='REPLACE'", IoSpecKind::File); // 12.5.6.10
|
|
CheckForProhibitedSpecifier(flags_.test(Flag::StatusScratch),
|
|
"STATUS='SCRATCH'", IoSpecKind::File); // 12.5.6.10
|
|
if (flags_.test(Flag::KnownStatus)) {
|
|
CheckForRequiredSpecifier(IoSpecKind::Newunit,
|
|
specifierSet_.test(IoSpecKind::File) ||
|
|
flags_.test(Flag::StatusScratch),
|
|
"FILE or STATUS='SCRATCH'"); // 12.5.6.12
|
|
} else {
|
|
CheckForRequiredSpecifier(IoSpecKind::Newunit,
|
|
specifierSet_.test(IoSpecKind::File) ||
|
|
specifierSet_.test(IoSpecKind::Status),
|
|
"FILE or STATUS"); // 12.5.6.12
|
|
}
|
|
if (flags_.test(Flag::KnownAccess)) {
|
|
CheckForRequiredSpecifier(flags_.test(Flag::AccessDirect),
|
|
"ACCESS='DIRECT'", IoSpecKind::Recl); // 12.5.6.15
|
|
CheckForProhibitedSpecifier(flags_.test(Flag::AccessStream),
|
|
"STATUS='STREAM'", IoSpecKind::Recl); // 12.5.6.15
|
|
}
|
|
CheckForUselessIomsg();
|
|
Done();
|
|
}
|
|
|
|
void IoChecker::Leave(const parser::PrintStmt &) {
|
|
CheckForPureSubprogram();
|
|
CheckForUselessIomsg();
|
|
Done();
|
|
}
|
|
|
|
static const parser::Name *FindNamelist(
|
|
const std::list<parser::IoControlSpec> &controls) {
|
|
for (const auto &control : controls) {
|
|
if (const parser::Name * namelist{std::get_if<parser::Name>(&control.u)}) {
|
|
if (namelist->symbol &&
|
|
namelist->symbol->GetUltimate().has<NamelistDetails>()) {
|
|
return namelist;
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static void CheckForDoVariable(
|
|
const parser::ReadStmt &readStmt, SemanticsContext &context) {
|
|
const std::list<parser::InputItem> &items{readStmt.items};
|
|
for (const auto &item : items) {
|
|
if (const parser::Variable *
|
|
variable{std::get_if<parser::Variable>(&item.u)}) {
|
|
context.CheckIndexVarRedefine(*variable);
|
|
}
|
|
}
|
|
}
|
|
|
|
void IoChecker::Leave(const parser::ReadStmt &readStmt) {
|
|
if (!flags_.test(Flag::InternalUnit)) {
|
|
CheckForPureSubprogram();
|
|
}
|
|
if (const parser::Name * namelist{FindNamelist(readStmt.controls)}) {
|
|
if (namelist->symbol) {
|
|
CheckNamelist(*namelist->symbol, common::DefinedIo::ReadFormatted,
|
|
namelist->source);
|
|
}
|
|
}
|
|
CheckForDoVariable(readStmt, context_);
|
|
if (!flags_.test(Flag::IoControlList)) {
|
|
Done();
|
|
return;
|
|
}
|
|
LeaveReadWrite();
|
|
CheckForProhibitedSpecifier(IoSpecKind::Delim); // C1212
|
|
CheckForProhibitedSpecifier(IoSpecKind::Sign); // C1212
|
|
CheckForProhibitedSpecifier(IoSpecKind::Rec, IoSpecKind::End); // C1220
|
|
if (specifierSet_.test(IoSpecKind::Size)) {
|
|
// F'2023 C1214 - allow with a warning
|
|
if (context_.ShouldWarn(common::LanguageFeature::ListDirectedSize)) {
|
|
if (specifierSet_.test(IoSpecKind::Nml)) {
|
|
context_.Say("If NML appears, SIZE should not appear"_port_en_US);
|
|
} else if (flags_.test(Flag::StarFmt)) {
|
|
context_.Say("If FMT=* appears, SIZE should not appear"_port_en_US);
|
|
}
|
|
}
|
|
}
|
|
CheckForRequiredSpecifier(IoSpecKind::Eor,
|
|
specifierSet_.test(IoSpecKind::Advance) && !flags_.test(Flag::AdvanceYes),
|
|
"ADVANCE with value 'NO'"); // C1222 + 12.6.2.1p2
|
|
CheckForRequiredSpecifier(IoSpecKind::Blank, flags_.test(Flag::FmtOrNml),
|
|
"FMT or NML"); // C1227
|
|
CheckForRequiredSpecifier(
|
|
IoSpecKind::Pad, flags_.test(Flag::FmtOrNml), "FMT or NML"); // C1227
|
|
Done();
|
|
}
|
|
|
|
void IoChecker::Leave(const parser::RewindStmt &) {
|
|
CheckForRequiredSpecifier(
|
|
flags_.test(Flag::NumberUnit), "UNIT number"); // C1240
|
|
CheckForPureSubprogram();
|
|
CheckForUselessIomsg();
|
|
Done();
|
|
}
|
|
|
|
void IoChecker::Leave(const parser::WaitStmt &) {
|
|
CheckForRequiredSpecifier(
|
|
flags_.test(Flag::NumberUnit), "UNIT number"); // C1237
|
|
CheckForPureSubprogram();
|
|
CheckForUselessIomsg();
|
|
Done();
|
|
}
|
|
|
|
void IoChecker::Leave(const parser::WriteStmt &writeStmt) {
|
|
if (!flags_.test(Flag::InternalUnit)) {
|
|
CheckForPureSubprogram();
|
|
}
|
|
if (const parser::Name * namelist{FindNamelist(writeStmt.controls)}) {
|
|
if (namelist->symbol) {
|
|
CheckNamelist(*namelist->symbol, common::DefinedIo::WriteFormatted,
|
|
namelist->source);
|
|
}
|
|
}
|
|
LeaveReadWrite();
|
|
CheckForProhibitedSpecifier(IoSpecKind::Blank); // C1213
|
|
CheckForProhibitedSpecifier(IoSpecKind::End); // C1213
|
|
CheckForProhibitedSpecifier(IoSpecKind::Eor); // C1213
|
|
CheckForProhibitedSpecifier(IoSpecKind::Pad); // C1213
|
|
CheckForProhibitedSpecifier(IoSpecKind::Size); // C1213
|
|
CheckForRequiredSpecifier(
|
|
IoSpecKind::Sign, flags_.test(Flag::FmtOrNml), "FMT or NML"); // C1227
|
|
CheckForRequiredSpecifier(IoSpecKind::Delim,
|
|
flags_.test(Flag::StarFmt) || specifierSet_.test(IoSpecKind::Nml),
|
|
"FMT=* or NML"); // C1228
|
|
Done();
|
|
}
|
|
|
|
void IoChecker::LeaveReadWrite() const {
|
|
CheckForRequiredSpecifier(IoSpecKind::Unit); // C1211
|
|
CheckForRequiredSpecifier(flags_.test(Flag::InternalUnit),
|
|
"UNIT=internal-file", flags_.test(Flag::FmtOrNml), "FMT or NML");
|
|
CheckForProhibitedSpecifier(IoSpecKind::Nml, IoSpecKind::Rec); // C1216
|
|
CheckForProhibitedSpecifier(IoSpecKind::Nml, IoSpecKind::Fmt); // C1216
|
|
CheckForProhibitedSpecifier(
|
|
IoSpecKind::Nml, flags_.test(Flag::DataList), "a data list"); // C1216
|
|
CheckForProhibitedSpecifier(flags_.test(Flag::InternalUnit),
|
|
"UNIT=internal-file", IoSpecKind::Pos); // C1219
|
|
CheckForProhibitedSpecifier(flags_.test(Flag::InternalUnit),
|
|
"UNIT=internal-file", IoSpecKind::Rec); // C1219
|
|
CheckForProhibitedSpecifier(
|
|
flags_.test(Flag::StarUnit), "UNIT=*", IoSpecKind::Pos); // C1219
|
|
CheckForProhibitedSpecifier(
|
|
flags_.test(Flag::StarUnit), "UNIT=*", IoSpecKind::Rec); // C1219
|
|
CheckForProhibitedSpecifier(
|
|
IoSpecKind::Rec, flags_.test(Flag::StarFmt), "FMT=*"); // C1220
|
|
CheckForRequiredSpecifier(IoSpecKind::Advance,
|
|
flags_.test(Flag::CharFmt) || flags_.test(Flag::LabelFmt) ||
|
|
flags_.test(Flag::AssignFmt),
|
|
"an explicit format"); // C1221
|
|
CheckForProhibitedSpecifier(IoSpecKind::Advance,
|
|
flags_.test(Flag::InternalUnit), "UNIT=internal-file"); // C1221
|
|
CheckForRequiredSpecifier(flags_.test(Flag::AsynchronousYes),
|
|
"ASYNCHRONOUS='YES'", flags_.test(Flag::NumberUnit),
|
|
"UNIT=number"); // C1224
|
|
CheckForRequiredSpecifier(IoSpecKind::Id, flags_.test(Flag::AsynchronousYes),
|
|
"ASYNCHRONOUS='YES'"); // C1225
|
|
CheckForProhibitedSpecifier(IoSpecKind::Pos, IoSpecKind::Rec); // C1226
|
|
CheckForRequiredSpecifier(IoSpecKind::Decimal, flags_.test(Flag::FmtOrNml),
|
|
"FMT or NML"); // C1227
|
|
CheckForRequiredSpecifier(IoSpecKind::Round, flags_.test(Flag::FmtOrNml),
|
|
"FMT or NML"); // C1227
|
|
CheckForUselessIomsg();
|
|
}
|
|
|
|
void IoChecker::SetSpecifier(IoSpecKind specKind) {
|
|
if (stmt_ == IoStmtKind::None) {
|
|
// FMT may appear on PRINT statements, which don't have any checks.
|
|
// [IO]MSG and [IO]STAT parse symbols are shared with non-I/O statements.
|
|
return;
|
|
}
|
|
// C1203, C1207, C1210, C1236, C1239, C1242, C1245
|
|
if (specifierSet_.test(specKind)) {
|
|
context_.Say("Duplicate %s specifier"_err_en_US,
|
|
parser::ToUpperCaseLetters(common::EnumToString(specKind)));
|
|
}
|
|
specifierSet_.set(specKind);
|
|
}
|
|
|
|
void IoChecker::CheckStringValue(IoSpecKind specKind, const std::string &value,
|
|
const parser::CharBlock &source) const {
|
|
static std::unordered_map<IoSpecKind, const std::set<std::string>> specValues{
|
|
{IoSpecKind::Access, {"DIRECT", "SEQUENTIAL", "STREAM"}},
|
|
{IoSpecKind::Action, {"READ", "READWRITE", "WRITE"}},
|
|
{IoSpecKind::Advance, {"NO", "YES"}},
|
|
{IoSpecKind::Asynchronous, {"NO", "YES"}},
|
|
{IoSpecKind::Blank, {"NULL", "ZERO"}},
|
|
{IoSpecKind::Decimal, {"COMMA", "POINT"}},
|
|
{IoSpecKind::Delim, {"APOSTROPHE", "NONE", "QUOTE"}},
|
|
{IoSpecKind::Encoding, {"DEFAULT", "UTF-8"}},
|
|
{IoSpecKind::Form, {"FORMATTED", "UNFORMATTED", "BINARY"}},
|
|
{IoSpecKind::Pad, {"NO", "YES"}},
|
|
{IoSpecKind::Position, {"APPEND", "ASIS", "REWIND"}},
|
|
{IoSpecKind::Round,
|
|
{"COMPATIBLE", "DOWN", "NEAREST", "PROCESSOR_DEFINED", "UP", "ZERO"}},
|
|
{IoSpecKind::Sign, {"PLUS", "PROCESSOR_DEFINED", "SUPPRESS"}},
|
|
{IoSpecKind::Status,
|
|
// Open values; Close values are {"DELETE", "KEEP"}.
|
|
{"NEW", "OLD", "REPLACE", "SCRATCH", "UNKNOWN"}},
|
|
{IoSpecKind::Carriagecontrol, {"LIST", "FORTRAN", "NONE"}},
|
|
{IoSpecKind::Convert, {"BIG_ENDIAN", "LITTLE_ENDIAN", "NATIVE", "SWAP"}},
|
|
{IoSpecKind::Dispose, {"DELETE", "KEEP"}},
|
|
};
|
|
auto upper{Normalize(value)};
|
|
if (specValues.at(specKind).count(upper) == 0) {
|
|
if (specKind == IoSpecKind::Access && upper == "APPEND") {
|
|
context_.Warn(common::LanguageFeature::OpenAccessAppend, source,
|
|
"ACCESS='%s' interpreted as POSITION='%s'"_port_en_US, value, upper);
|
|
} else {
|
|
context_.Say(source, "Invalid %s value '%s'"_err_en_US,
|
|
parser::ToUpperCaseLetters(common::EnumToString(specKind)), value);
|
|
}
|
|
}
|
|
}
|
|
|
|
// CheckForRequiredSpecifier and CheckForProhibitedSpecifier functions
|
|
// need conditions to check, and string arguments to insert into a message.
|
|
// An IoSpecKind provides both an absence/presence condition and a string
|
|
// argument (its name). A (condition, string) pair provides an arbitrary
|
|
// condition and an arbitrary string.
|
|
|
|
void IoChecker::CheckForRequiredSpecifier(IoSpecKind specKind) const {
|
|
if (!specifierSet_.test(specKind)) {
|
|
context_.Say("%s statement must have a %s specifier"_err_en_US,
|
|
parser::ToUpperCaseLetters(common::EnumToString(stmt_)),
|
|
parser::ToUpperCaseLetters(common::EnumToString(specKind)));
|
|
}
|
|
}
|
|
|
|
void IoChecker::CheckForRequiredSpecifier(
|
|
bool condition, const std::string &s) const {
|
|
if (!condition) {
|
|
context_.Say("%s statement must have a %s specifier"_err_en_US,
|
|
parser::ToUpperCaseLetters(common::EnumToString(stmt_)), s);
|
|
}
|
|
}
|
|
|
|
void IoChecker::CheckForRequiredSpecifier(
|
|
IoSpecKind specKind1, IoSpecKind specKind2) const {
|
|
if (specifierSet_.test(specKind1) && !specifierSet_.test(specKind2)) {
|
|
context_.Say("If %s appears, %s must also appear"_err_en_US,
|
|
parser::ToUpperCaseLetters(common::EnumToString(specKind1)),
|
|
parser::ToUpperCaseLetters(common::EnumToString(specKind2)));
|
|
}
|
|
}
|
|
|
|
void IoChecker::CheckForRequiredSpecifier(
|
|
IoSpecKind specKind, bool condition, const std::string &s) const {
|
|
if (specifierSet_.test(specKind) && !condition) {
|
|
context_.Say("If %s appears, %s must also appear"_err_en_US,
|
|
parser::ToUpperCaseLetters(common::EnumToString(specKind)), s);
|
|
}
|
|
}
|
|
|
|
void IoChecker::CheckForRequiredSpecifier(
|
|
bool condition, const std::string &s, IoSpecKind specKind) const {
|
|
if (condition && !specifierSet_.test(specKind)) {
|
|
context_.Say("If %s appears, %s must also appear"_err_en_US, s,
|
|
parser::ToUpperCaseLetters(common::EnumToString(specKind)));
|
|
}
|
|
}
|
|
|
|
void IoChecker::CheckForRequiredSpecifier(bool condition1,
|
|
const std::string &s1, bool condition2, const std::string &s2) const {
|
|
if (condition1 && !condition2) {
|
|
context_.Say("If %s appears, %s must also appear"_err_en_US, s1, s2);
|
|
}
|
|
}
|
|
|
|
void IoChecker::CheckForProhibitedSpecifier(IoSpecKind specKind) const {
|
|
if (specifierSet_.test(specKind)) {
|
|
context_.Say("%s statement must not have a %s specifier"_err_en_US,
|
|
parser::ToUpperCaseLetters(common::EnumToString(stmt_)),
|
|
parser::ToUpperCaseLetters(common::EnumToString(specKind)));
|
|
}
|
|
}
|
|
|
|
void IoChecker::CheckForProhibitedSpecifier(
|
|
IoSpecKind specKind1, IoSpecKind specKind2) const {
|
|
if (specifierSet_.test(specKind1) && specifierSet_.test(specKind2)) {
|
|
context_.Say("If %s appears, %s must not appear"_err_en_US,
|
|
parser::ToUpperCaseLetters(common::EnumToString(specKind1)),
|
|
parser::ToUpperCaseLetters(common::EnumToString(specKind2)));
|
|
}
|
|
}
|
|
|
|
void IoChecker::CheckForProhibitedSpecifier(
|
|
IoSpecKind specKind, bool condition, const std::string &s) const {
|
|
if (specifierSet_.test(specKind) && condition) {
|
|
context_.Say("If %s appears, %s must not appear"_err_en_US,
|
|
parser::ToUpperCaseLetters(common::EnumToString(specKind)), s);
|
|
}
|
|
}
|
|
|
|
void IoChecker::CheckForProhibitedSpecifier(
|
|
bool condition, const std::string &s, IoSpecKind specKind) const {
|
|
if (condition && specifierSet_.test(specKind)) {
|
|
context_.Say("If %s appears, %s must not appear"_err_en_US, s,
|
|
parser::ToUpperCaseLetters(common::EnumToString(specKind)));
|
|
}
|
|
}
|
|
|
|
template <typename A>
|
|
void IoChecker::CheckForDefinableVariable(
|
|
const A &variable, const std::string &s) const {
|
|
if (const auto *var{parser::Unwrap<parser::Variable>(variable)}) {
|
|
if (auto expr{AnalyzeExpr(context_, *var)}) {
|
|
auto at{var->GetSource()};
|
|
if (auto whyNot{WhyNotDefinable(at, context_.FindScope(at),
|
|
DefinabilityFlags{DefinabilityFlag::VectorSubscriptIsOk},
|
|
*expr)}) {
|
|
if (whyNot->IsFatal()) {
|
|
const Symbol *base{GetFirstSymbol(*expr)};
|
|
context_
|
|
.Say(at, "%s variable '%s' is not definable"_err_en_US, s,
|
|
(base ? base->name() : at).ToString())
|
|
.Attach(
|
|
std::move(whyNot->set_severity(parser::Severity::Because)));
|
|
} else {
|
|
context_.Say(std::move(*whyNot));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void IoChecker::CheckForPureSubprogram() const { // C1597
|
|
CHECK(context_.location());
|
|
const Scope &scope{context_.FindScope(*context_.location())};
|
|
if (FindPureProcedureContaining(scope)) {
|
|
context_.Say("External I/O is not allowed in a pure subprogram"_err_en_US);
|
|
}
|
|
}
|
|
|
|
void IoChecker::CheckForUselessIomsg() const {
|
|
if (specifierSet_.test(IoSpecKind::Iomsg) &&
|
|
!specifierSet_.test(IoSpecKind::Err) &&
|
|
!specifierSet_.test(IoSpecKind::Iostat) &&
|
|
context_.ShouldWarn(common::UsageWarning::UselessIomsg)) {
|
|
context_.Say("IOMSG= is useless without either ERR= or IOSTAT="_warn_en_US);
|
|
}
|
|
}
|
|
|
|
// Seeks out an allocatable or pointer ultimate component that is not
|
|
// nested in a nonallocatable/nonpointer component with a specific
|
|
// defined I/O procedure.
|
|
static const Symbol *FindUnsafeIoDirectComponent(common::DefinedIo which,
|
|
const DerivedTypeSpec &derived, const Scope &scope) {
|
|
if (HasDefinedIo(which, derived, &scope)) {
|
|
return nullptr;
|
|
}
|
|
if (const Scope * dtScope{derived.scope()}) {
|
|
for (const auto &pair : *dtScope) {
|
|
const Symbol &symbol{*pair.second};
|
|
if (IsAllocatableOrPointer(symbol)) {
|
|
return &symbol;
|
|
}
|
|
if (const auto *details{symbol.detailsIf<ObjectEntityDetails>()}) {
|
|
if (const DeclTypeSpec * type{details->type()}) {
|
|
if (type->category() == DeclTypeSpec::Category::TypeDerived) {
|
|
const DerivedTypeSpec &componentDerived{type->derivedTypeSpec()};
|
|
if (const Symbol *
|
|
bad{FindUnsafeIoDirectComponent(
|
|
which, componentDerived, scope)}) {
|
|
return bad;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
// For a type that does not have a defined I/O subroutine, finds a direct
|
|
// component that is a witness to an accessibility violation outside the module
|
|
// in which the type was defined.
|
|
static const Symbol *FindInaccessibleComponent(common::DefinedIo which,
|
|
const DerivedTypeSpec &derived, const Scope &scope) {
|
|
if (const Scope * dtScope{derived.scope()}) {
|
|
if (const Scope * module{FindModuleContaining(*dtScope)}) {
|
|
for (const auto &pair : *dtScope) {
|
|
const Symbol &symbol{*pair.second};
|
|
if (IsAllocatableOrPointer(symbol)) {
|
|
continue; // already an error
|
|
}
|
|
if (const auto *details{symbol.detailsIf<ObjectEntityDetails>()}) {
|
|
const DerivedTypeSpec *componentDerived{nullptr};
|
|
if (const DeclTypeSpec * type{details->type()}) {
|
|
if (type->category() == DeclTypeSpec::Category::TypeDerived) {
|
|
componentDerived = &type->derivedTypeSpec();
|
|
}
|
|
}
|
|
if (componentDerived &&
|
|
HasDefinedIo(which, *componentDerived, &scope)) {
|
|
continue; // this component and its descendents are fine
|
|
}
|
|
if (symbol.attrs().test(Attr::PRIVATE) &&
|
|
!symbol.test(Symbol::Flag::ParentComp)) {
|
|
if (!DoesScopeContain(module, scope)) {
|
|
return &symbol;
|
|
}
|
|
}
|
|
if (componentDerived) {
|
|
if (const Symbol *
|
|
bad{FindInaccessibleComponent(
|
|
which, *componentDerived, scope)}) {
|
|
return bad;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
// Fortran 2018, 12.6.3 paragraphs 5 & 7
|
|
parser::Message *IoChecker::CheckForBadIoType(const evaluate::DynamicType &type,
|
|
common::DefinedIo which, parser::CharBlock where) const {
|
|
if (type.IsUnlimitedPolymorphic()) {
|
|
return &context_.Say(
|
|
where, "I/O list item may not be unlimited polymorphic"_err_en_US);
|
|
} else if (type.category() == TypeCategory::Derived) {
|
|
const auto &derived{type.GetDerivedTypeSpec()};
|
|
const Scope &scope{context_.FindScope(where)};
|
|
if (const Symbol *
|
|
bad{FindUnsafeIoDirectComponent(which, derived, scope)}) {
|
|
return &context_.SayWithDecl(*bad, where,
|
|
"Derived type '%s' in I/O cannot have an allocatable or pointer direct component '%s' unless using defined I/O"_err_en_US,
|
|
derived.name(), bad->name());
|
|
}
|
|
if (!HasDefinedIo(which, derived, &scope)) {
|
|
if (type.IsPolymorphic()) {
|
|
return &context_.Say(where,
|
|
"Derived type '%s' in I/O may not be polymorphic unless using defined I/O"_err_en_US,
|
|
derived.name());
|
|
}
|
|
if ((IsBuiltinDerivedType(&derived, "c_ptr") ||
|
|
IsBuiltinDerivedType(&derived, "c_devptr")) &&
|
|
!context_.ShouldWarn(common::LanguageFeature::PrintCptr)) {
|
|
// Bypass the check below for c_ptr and c_devptr.
|
|
return nullptr;
|
|
}
|
|
if (const Symbol *
|
|
bad{FindInaccessibleComponent(which, derived, scope)}) {
|
|
return &context_.Say(where,
|
|
"I/O of the derived type '%s' may not be performed without defined I/O in a scope in which a direct component like '%s' is inaccessible"_err_en_US,
|
|
derived.name(), bad->name());
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void IoChecker::CheckForBadIoType(const SomeExpr &expr, common::DefinedIo which,
|
|
parser::CharBlock where) const {
|
|
if (auto type{expr.GetType()}) {
|
|
CheckForBadIoType(*type, which, where);
|
|
}
|
|
}
|
|
|
|
parser::Message *IoChecker::CheckForBadIoType(const Symbol &symbol,
|
|
common::DefinedIo which, parser::CharBlock where) const {
|
|
if (auto type{evaluate::DynamicType::From(symbol)}) {
|
|
if (auto *msg{CheckForBadIoType(*type, which, where)}) {
|
|
evaluate::AttachDeclaration(*msg, symbol);
|
|
return msg;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void IoChecker::CheckNamelist(const Symbol &namelist, common::DefinedIo which,
|
|
parser::CharBlock namelistLocation) const {
|
|
if (!context_.HasError(namelist)) {
|
|
const auto &details{namelist.GetUltimate().get<NamelistDetails>()};
|
|
for (const Symbol &object : details.objects()) {
|
|
context_.CheckIndexVarRedefine(namelistLocation, object);
|
|
if (auto *msg{CheckForBadIoType(object, which, namelistLocation)}) {
|
|
evaluate::AttachDeclaration(*msg, namelist);
|
|
} else if (which == common::DefinedIo::ReadFormatted) {
|
|
if (auto why{WhyNotDefinable(namelistLocation, namelist.owner(),
|
|
DefinabilityFlags{}, object)}) {
|
|
context_
|
|
.Say(namelistLocation,
|
|
"NAMELIST input group must not contain undefinable item '%s'"_err_en_US,
|
|
object.name())
|
|
.Attach(std::move(why->set_severity(parser::Severity::Because)));
|
|
context_.SetError(namelist);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace Fortran::semantics
|