[flang] Check constraint C1577 for statement functions

Check most of the requiremens of constraint C1577 for statement functions.
The restrictions that prevent recursion are hard errors; the others seem
to be benign legacies and are caught as portability warnings.

Differential Revision: https://reviews.llvm.org/D139136
This commit is contained in:
Peter Klausler 2022-11-10 10:24:38 -08:00
parent c95922c717
commit 1623aee41a
9 changed files with 176 additions and 24 deletions

View File

@ -108,5 +108,8 @@ bool IsSimplyContiguous(const A &x, FoldingContext &context) {
template <typename A> bool IsErrorExpr(const A &);
extern template bool IsErrorExpr(const Expr<SomeType> &);
std::optional<parser::Message> CheckStatementFunction(
const Symbol &, const Expr<SomeType> &, FoldingContext &);
} // namespace Fortran::evaluate
#endif

View File

@ -237,6 +237,7 @@ public:
MaybeExpr Analyze(const parser::StructureConstructor &);
MaybeExpr Analyze(const parser::InitialDataTarget &);
MaybeExpr Analyze(const parser::NullInit &);
MaybeExpr Analyze(const parser::StmtFunctionStmt &);
void Analyze(const parser::CallStmt &);
const Assignment *Analyze(const parser::AssignmentStmt &);
@ -385,6 +386,7 @@ private:
bool useSavedTypedExprs_{true};
bool inWhereBody_{false};
bool inDataStmtConstant_{false};
bool inStmtFunctionDefinition_{false};
friend class ArgumentAnalyzer;
};

View File

@ -523,9 +523,9 @@ std::string DetailsToString(const Details &);
class Symbol {
public:
ENUM_CLASS(Flag,
Function, // symbol is a function
Function, // symbol is a function or statement function
Subroutine, // symbol is a subroutine
StmtFunction, // symbol is a statement function (Function is set too)
StmtFunction, // symbol is a statement function or result
Implicit, // symbol is implicitly typed
ImplicitOrError, // symbol must be implicitly typed or it's an error
ModFile, // symbol came from .mod file

View File

@ -871,4 +871,83 @@ template <typename A> bool IsErrorExpr(const A &x) {
template bool IsErrorExpr(const Expr<SomeType> &);
// C1577
// TODO: Also check C1579 & C1582 here
class StmtFunctionChecker
: public AnyTraverse<StmtFunctionChecker, std::optional<parser::Message>> {
public:
using Result = std::optional<parser::Message>;
using Base = AnyTraverse<StmtFunctionChecker, Result>;
StmtFunctionChecker(const Symbol &sf, FoldingContext &context)
: Base{*this}, sf_{sf}, context_{context} {}
using Base::operator();
template <typename T> Result operator()(const ArrayConstructor<T> &) const {
return parser::Message{sf_.name(),
"Statement function '%s' should not contain an array constructor"_port_en_US,
sf_.name()};
}
Result operator()(const StructureConstructor &) const {
return parser::Message{sf_.name(),
"Statement function '%s' should not contain a structure constructor"_port_en_US,
sf_.name()};
}
Result operator()(const TypeParamInquiry &) const {
return parser::Message{sf_.name(),
"Statement function '%s' should not contain a type parameter inquiry"_port_en_US,
sf_.name()};
}
Result operator()(const ProcedureDesignator &proc) const {
if (const Symbol * symbol{proc.GetSymbol()}) {
const Symbol &ultimate{symbol->GetUltimate()};
if (const auto *subp{
ultimate.detailsIf<semantics::SubprogramDetails>()}) {
if (subp->stmtFunction() && &ultimate.owner() == &sf_.owner()) {
if (ultimate.name().begin() > sf_.name().begin()) {
return parser::Message{sf_.name(),
"Statement function '%s' may not reference another statement function '%s' that is defined later"_err_en_US,
sf_.name(), ultimate.name()};
}
}
}
if (auto chars{
characteristics::Procedure::Characterize(proc, context_)}) {
if (!chars->CanBeCalledViaImplicitInterface()) {
return parser::Message(sf_.name(),
"Statement function '%s' should not reference function '%s' that requires an explicit interface"_port_en_US,
sf_.name(), symbol->name());
}
}
}
if (proc.Rank() > 0) {
return parser::Message(sf_.name(),
"Statement function '%s' should not reference a function that returns an array"_port_en_US,
sf_.name());
}
return std::nullopt;
}
Result operator()(const ActualArgument &arg) const {
if (const auto *expr{arg.UnwrapExpr()}) {
if (auto result{(*this)(*expr)}) {
return result;
}
if (expr->Rank() > 0 && !UnwrapWholeSymbolOrComponentDataRef(*expr)) {
return parser::Message(sf_.name(),
"Statement function '%s' should not pass an array argument that is not a whole array"_port_en_US,
sf_.name());
}
}
return std::nullopt;
}
private:
const Symbol &sf_;
FoldingContext &context_;
};
std::optional<parser::Message> CheckStatementFunction(
const Symbol &sf, const Expr<SomeType> &expr, FoldingContext &context) {
return StmtFunctionChecker{sf, context}(expr);
}
} // namespace Fortran::evaluate

View File

@ -1272,6 +1272,9 @@ bool IsPureProcedure(const Symbol &original) {
// reference an IMPURE procedure or a VOLATILE variable
if (const auto &expr{symbol.get<SubprogramDetails>().stmtFunction()}) {
for (const SymbolRef &ref : evaluate::CollectSymbols(*expr)) {
if (&*ref == &symbol) {
return false; // error recovery, recursion is caught elsewhere
}
if (IsFunction(*ref) && !IsPureProcedure(*ref)) {
return false;
}

View File

@ -965,6 +965,12 @@ void CheckHelper::CheckSubprogram(
}
}
}
if (const MaybeExpr & stmtFunction{details.stmtFunction()}) {
if (auto msg{evaluate::CheckStatementFunction(
symbol, *stmtFunction, context_.foldingContext())}) {
SayWithDeclaration(symbol, std::move(*msg));
}
}
if (IsElementalProcedure(symbol)) {
// See comment on the similar check in CheckProcEntity()
if (details.isDummy()) {

View File

@ -852,6 +852,12 @@ MaybeExpr ExpressionAnalyzer::Analyze(const parser::NullInit &n) {
return std::nullopt;
}
MaybeExpr ExpressionAnalyzer::Analyze(
const parser::StmtFunctionStmt &stmtFunc) {
inStmtFunctionDefinition_ = true;
return Analyze(std::get<parser::Scalar<parser::Expr>>(stmtFunc.t));
}
MaybeExpr ExpressionAnalyzer::Analyze(const parser::InitialDataTarget &x) {
return Analyze(x.value());
}
@ -2187,6 +2193,9 @@ bool ExpressionAnalyzer::ResolveForward(const Symbol &symbol) {
context_.SetError(symbol);
return false;
}
} else if (inStmtFunctionDefinition_) {
semantics::ResolveSpecificationParts(context_, symbol);
CHECK(symbol.has<semantics::SubprogramDetails>());
} else { // 10.1.11 para 4
Say("The internal function '%s' may not be referenced in a specification expression"_err_en_US,
symbol.name());
@ -3076,7 +3085,9 @@ static bool CheckFuncRefToArrayElement(semantics::SemanticsContext &context,
if (const Symbol *function{
semantics::IsFunctionResultWithSameNameAsFunction(*name->symbol)}) {
auto &msg{context.Say(funcRef.v.source,
"Recursive call to '%s' requires a distinct RESULT in its declaration"_err_en_US,
function->flags().test(Symbol::Flag::StmtFunction)
? "Recursive call to statement function '%s' is not allowed"_err_en_US
: "Recursive call to '%s' requires a distinct RESULT in its declaration"_err_en_US,
name->source)};
AttachDeclaration(&msg, *function);
name->symbol = const_cast<Symbol *>(function);

View File

@ -3307,7 +3307,8 @@ bool SubprogramVisitor::HandleStmtFunction(const parser::StmtFunctionStmt &x) {
// Look up name: provides return type or tells us if it's an array
if (auto *symbol{FindSymbol(name)}) {
auto *details{symbol->detailsIf<EntityDetails>()};
if (!details) {
if (!details || symbol->has<ObjectEntityDetails>() ||
symbol->has<ProcEntityDetails>()) {
badStmtFuncFound_ = true;
return false;
}
@ -3317,7 +3318,7 @@ bool SubprogramVisitor::HandleStmtFunction(const parser::StmtFunctionStmt &x) {
}
if (badStmtFuncFound_) {
Say(name, "'%s' has not been declared as an array"_err_en_US);
return true;
return false;
}
auto &symbol{PushSubprogramScope(name, Symbol::Flag::Function)};
symbol.set(Symbol::Flag::StmtFunction);
@ -3342,10 +3343,9 @@ bool SubprogramVisitor::HandleStmtFunction(const parser::StmtFunctionStmt &x) {
}
resultDetails.set_funcResult(true);
Symbol &result{MakeSymbol(name, std::move(resultDetails))};
result.flags().set(Symbol::Flag::StmtFunction);
ApplyImplicitRules(result);
details.set_result(result);
const auto &parsedExpr{std::get<parser::Scalar<parser::Expr>>(x.t)};
Walk(parsedExpr);
// The analysis of the expression that constitutes the body of the
// statement function is deferred to FinishSpecificationPart() so that
// all declarations and implicit typing are complete.
@ -7414,28 +7414,31 @@ void ResolveNamesVisitor::FinishSpecificationPart(
// Analyze the bodies of statement functions now that the symbols in this
// specification part have been fully declared and implicitly typed.
// (Statement function references are not allowed in specification
// expressions, so it's safe to defer processing their definitions.)
void ResolveNamesVisitor::AnalyzeStmtFunctionStmt(
const parser::StmtFunctionStmt &stmtFunc) {
Symbol *symbol{std::get<parser::Name>(stmtFunc.t).symbol};
if (!symbol || !symbol->has<SubprogramDetails>()) {
auto *details{symbol ? symbol->detailsIf<SubprogramDetails>() : nullptr};
if (!details || !symbol->scope()) {
return;
}
auto &details{symbol->get<SubprogramDetails>()};
auto expr{AnalyzeExpr(
context(), std::get<parser::Scalar<parser::Expr>>(stmtFunc.t))};
if (!expr) {
context().SetError(*symbol);
return;
}
if (auto type{evaluate::DynamicType::From(*symbol)}) {
auto converted{ConvertToType(*type, std::move(*expr))};
if (!converted) {
context().SetError(*symbol);
return;
// Resolve the symbols on the RHS of the statement function.
PushScope(*symbol->scope());
const auto &parsedExpr{std::get<parser::Scalar<parser::Expr>>(stmtFunc.t)};
Walk(parsedExpr);
PopScope();
if (auto expr{AnalyzeExpr(context(), stmtFunc)}) {
if (auto type{evaluate::DynamicType::From(*symbol)}) {
if (auto converted{ConvertToType(*type, std::move(*expr))}) {
details->set_stmtFunction(std::move(*converted));
}
} else {
details->set_stmtFunction(std::move(*expr));
}
details.set_stmtFunction(std::move(*converted));
} else {
details.set_stmtFunction(std::move(*expr));
}
if (!details->stmtFunction()) {
context().SetError(*symbol);
}
}
@ -7825,6 +7828,7 @@ public:
resolver_.CheckBindings(tbps);
}
}
bool Pre(const parser::StmtFunctionStmt &stmtFunc) { return false; }
private:
void Init(const parser::Name &name,
@ -7849,7 +7853,7 @@ void ResolveNamesVisitor::FinishSpecificationParts(const ProgramTree &node) {
}
SetScope(*node.scope());
// The initializers of pointers, the default initializers of pointer
// components, and non-deferred type-bound procedure bindings have not
// components, non-deferred type-bound procedure bindings have not
// yet been traversed.
// We do that now, when any (formerly) forward references that appear
// in those initializers will resolve to the right symbols without

View File

@ -0,0 +1,44 @@
! RUN: %python %S/test_errors.py %s %flang_fc1
! C1577
program main
type t1(k,l)
integer, kind :: k = kind(1)
integer, len :: l = 666
integer(k) n
end type t1
interface
pure integer function ifunc()
end function
end interface
type(t1(k=4,l=ifunc())) x1
!PORTABILITY: Statement function 'sf1' should not contain an array constructor
sf1(n) = sum([(j,j=1,n)])
type(t1) sf2
!PORTABILITY: Statement function 'sf2' should not contain a structure constructor
sf2(n) = t1(n)
!PORTABILITY: Statement function 'sf3' should not contain a type parameter inquiry
sf3(n) = x1%l
!ERROR: Recursive call to statement function 'sf4' is not allowed
sf4(n) = sf4(n)
!ERROR: Statement function 'sf5' may not reference another statement function 'sf6' that is defined later
sf5(n) = sf6(n)
real sf7
!ERROR: Statement function 'sf6' may not reference another statement function 'sf7' that is defined later
sf6(n) = sf7(n)
!PORTABILITY: Statement function 'sf7' should not reference function 'explicit' that requires an explicit interface
sf7(n) = explicit(n)
real :: a(3) = [1., 2., 3.]
!PORTABILITY: Statement function 'sf8' should not pass an array argument that is not a whole array
sf8(n) = sum(a(1:2))
sf8a(n) = sum(a) ! ok
contains
real function explicit(x,y)
integer, intent(in) :: x
integer, intent(in), optional :: y
explicit = x
end function
pure function arr()
real :: arr(2)
arr = [1., 2.]
end function
end