From 7e225423d39ae1982e9380a4a0836888ab6b3bd8 Mon Sep 17 00:00:00 2001 From: Peter Klausler Date: Fri, 15 Apr 2022 13:23:16 -0700 Subject: [PATCH] [flang] Finer control over error recovery with GetExpr() Prior to this patch, the semantics utility GetExpr() will crash unconditionally if it encounters a typed expression in the parse tree that has not been set by expression semantics. This is the right behavior when called from lowering, by which time it is known that the program had no fatal user errors, since it signifies a fatal internal error. However, prior to lowering, in the statement semantics checking code, a more nuanced test should be used before crashing -- specifically, we should not crash in the face of a missing typed expression when in error recovery mode. Getting this right requires GetExpr() and its helper class to have access to the semantics context, so that it can check AnyFatalErrors() before crashing. So this patch touches nearly all of its call sites. Differential Revision: https://reviews.llvm.org/D123873 --- flang/include/flang/Semantics/tools.h | 44 +++++++++++++++------ flang/lib/Semantics/assignment.cpp | 2 +- flang/lib/Semantics/check-allocate.cpp | 2 +- flang/lib/Semantics/check-arithmeticif.cpp | 2 +- flang/lib/Semantics/check-case.cpp | 2 +- flang/lib/Semantics/check-coarray.cpp | 2 +- flang/lib/Semantics/check-deallocate.cpp | 2 +- flang/lib/Semantics/check-do-forall.cpp | 10 ++--- flang/lib/Semantics/check-io.cpp | 8 ++-- flang/lib/Semantics/check-io.h | 2 +- flang/lib/Semantics/check-nullify.cpp | 2 +- flang/lib/Semantics/check-omp-structure.cpp | 8 ++-- flang/lib/Semantics/check-stop.cpp | 2 +- flang/lib/Semantics/data-to-inits.cpp | 22 ++++++----- flang/lib/Semantics/resolve-directives.cpp | 12 ++++++ flang/lib/Semantics/tools.cpp | 34 ++++++++-------- 16 files changed, 97 insertions(+), 59 deletions(-) diff --git a/flang/include/flang/Semantics/tools.h b/flang/include/flang/Semantics/tools.h index b84afe581a5a..1ee21041dcc2 100644 --- a/flang/include/flang/Semantics/tools.h +++ b/flang/include/flang/Semantics/tools.h @@ -255,22 +255,25 @@ bool ExprHasTypeCategory( bool ExprTypeKindIsDefault( const SomeExpr &expr, const SemanticsContext &context); -struct GetExprHelper { - // Specializations for parse tree nodes that have a typedExpr member. - static const SomeExpr *Get(const parser::Expr &); - static const SomeExpr *Get(const parser::Variable &); - static const SomeExpr *Get(const parser::DataStmtConstant &); - static const SomeExpr *Get(const parser::AllocateObject &); - static const SomeExpr *Get(const parser::PointerObject &); +class GetExprHelper { +public: + explicit GetExprHelper(SemanticsContext *context) : context_{context} {} + GetExprHelper() : crashIfNoExpr_{true} {} - template - static const SomeExpr *Get(const common::Indirection &x) { + // Specializations for parse tree nodes that have a typedExpr member. + const SomeExpr *Get(const parser::Expr &); + const SomeExpr *Get(const parser::Variable &); + const SomeExpr *Get(const parser::DataStmtConstant &); + const SomeExpr *Get(const parser::AllocateObject &); + const SomeExpr *Get(const parser::PointerObject &); + + template const SomeExpr *Get(const common::Indirection &x) { return Get(x.value()); } - template static const SomeExpr *Get(const std::optional &x) { + template const SomeExpr *Get(const std::optional &x) { return x ? Get(*x) : nullptr; } - template static const SomeExpr *Get(const T &x) { + template const SomeExpr *Get(const T &x) { static_assert( !parser::HasTypedExpr::value, "explicit Get overload must be added"); if constexpr (ConstraintTrait) { @@ -281,8 +284,25 @@ struct GetExprHelper { return nullptr; } } + +private: + SemanticsContext *context_{nullptr}; + const bool crashIfNoExpr_{false}; }; +// If a SemanticsContext is passed, even if null, it is possible for a null +// pointer to be returned in the event of an expression that had fatal errors. +// Use these first two forms in semantics checks for best error recovery. +// If a SemanticsContext is not passed, a missing expression will +// cause a crash. +template +const SomeExpr *GetExpr(SemanticsContext *context, const T &x) { + return GetExprHelper{context}.Get(x); +} +template +const SomeExpr *GetExpr(SemanticsContext &context, const T &x) { + return GetExprHelper{&context}.Get(x); +} template const SomeExpr *GetExpr(const T &x) { return GetExprHelper{}.Get(x); } @@ -292,7 +312,7 @@ const evaluate::Assignment *GetAssignment( const parser::PointerAssignmentStmt &); template std::optional GetIntValue(const T &x) { - if (const auto *expr{GetExpr(x)}) { + if (const auto *expr{GetExpr(nullptr, x)}) { return evaluate::ToInt64(*expr); } else { return std::nullopt; diff --git a/flang/lib/Semantics/assignment.cpp b/flang/lib/Semantics/assignment.cpp index 1fae92c61f3c..650f31b2dacf 100644 --- a/flang/lib/Semantics/assignment.cpp +++ b/flang/lib/Semantics/assignment.cpp @@ -246,7 +246,7 @@ void AssignmentContext::CheckShape(parser::CharBlock at, const SomeExpr *expr) { template void AssignmentContext::PushWhereContext(const A &x) { const auto &expr{std::get(x.t)}; - CheckShape(expr.thing.value().source, GetExpr(expr)); + CheckShape(expr.thing.value().source, GetExpr(context_, expr)); ++whereDepth_; } diff --git a/flang/lib/Semantics/check-allocate.cpp b/flang/lib/Semantics/check-allocate.cpp index 8a3a23ad4f8c..63a1132630bb 100644 --- a/flang/lib/Semantics/check-allocate.cpp +++ b/flang/lib/Semantics/check-allocate.cpp @@ -187,7 +187,7 @@ static std::optional CheckAllocateOptions( } if (info.gotSource || info.gotMold) { - if (const auto *expr{GetExpr(DEREF(parserSourceExpr))}) { + if (const auto *expr{GetExpr(context, DEREF(parserSourceExpr))}) { parser::CharBlock at{parserSourceExpr->source}; info.sourceExprType = expr->GetType(); if (!info.sourceExprType) { diff --git a/flang/lib/Semantics/check-arithmeticif.cpp b/flang/lib/Semantics/check-arithmeticif.cpp index e8ce6aab60c9..f87a0045fff5 100644 --- a/flang/lib/Semantics/check-arithmeticif.cpp +++ b/flang/lib/Semantics/check-arithmeticif.cpp @@ -25,7 +25,7 @@ void ArithmeticIfStmtChecker::Leave( // R853 Check for a scalar-numeric-expr // C849 that shall not be of type complex. auto &parsedExpr{std::get(arithmeticIfStmt.t)}; - if (const auto *expr{GetExpr(parsedExpr)}) { + if (const auto *expr{GetExpr(context_, parsedExpr)}) { if (expr->Rank() > 0) { context_.Say(parsedExpr.source, "ARITHMETIC IF expression must be a scalar expression"_err_en_US); diff --git a/flang/lib/Semantics/check-case.cpp b/flang/lib/Semantics/check-case.cpp index 262a68596ae7..bd1bb073539d 100644 --- a/flang/lib/Semantics/check-case.cpp +++ b/flang/lib/Semantics/check-case.cpp @@ -240,7 +240,7 @@ void CaseChecker::Enter(const parser::CaseConstruct &construct) { const auto &selectCase{selectCaseStmt.statement}; const auto &selectExpr{ std::get>(selectCase.t).thing}; - const auto *x{GetExpr(selectExpr)}; + const auto *x{GetExpr(context_, selectExpr)}; if (!x) { return; // expression semantics failed } diff --git a/flang/lib/Semantics/check-coarray.cpp b/flang/lib/Semantics/check-coarray.cpp index 4ce286d0bca1..6dc4640d6186 100644 --- a/flang/lib/Semantics/check-coarray.cpp +++ b/flang/lib/Semantics/check-coarray.cpp @@ -64,7 +64,7 @@ private: template static void CheckTeamType(SemanticsContext &context, const T &x) { - if (const auto *expr{GetExpr(x)}) { + if (const auto *expr{GetExpr(context, x)}) { if (!IsTeamType(evaluate::GetDerivedTypeSpec(expr->GetType()))) { context.Say(parser::FindSourceLocation(x), // C1114 "Team value must be of type TEAM_TYPE from module ISO_FORTRAN_ENV"_err_en_US); diff --git a/flang/lib/Semantics/check-deallocate.cpp b/flang/lib/Semantics/check-deallocate.cpp index 03c2d6ebddda..bc8d123786cf 100644 --- a/flang/lib/Semantics/check-deallocate.cpp +++ b/flang/lib/Semantics/check-deallocate.cpp @@ -36,7 +36,7 @@ void DeallocateChecker::Leave(const parser::DeallocateStmt &deallocateStmt) { [&](const parser::StructureComponent &structureComponent) { // Only perform structureComponent checks it was successfully // analyzed in expression analysis. - if (GetExpr(allocateObject)) { + if (GetExpr(context_, allocateObject)) { if (!IsAllocatableOrPointer( *structureComponent.component.symbol)) { // C932 context_.Say(structureComponent.component.source, diff --git a/flang/lib/Semantics/check-do-forall.cpp b/flang/lib/Semantics/check-do-forall.cpp index e1cc4266e802..fada82df46d7 100644 --- a/flang/lib/Semantics/check-do-forall.cpp +++ b/flang/lib/Semantics/check-do-forall.cpp @@ -501,7 +501,7 @@ private: // Semantic checks for the limit and step expressions void CheckDoExpression(const parser::ScalarExpr &scalarExpression) { - if (const SomeExpr * expr{GetExpr(scalarExpression)}) { + if (const SomeExpr * expr{GetExpr(context_, scalarExpression)}) { if (!ExprHasTypeCategory(*expr, TypeCategory::Integer)) { // No warnings or errors for type INTEGER const parser::CharBlock &loc{scalarExpression.thing.value().source}; @@ -569,10 +569,10 @@ private: return symbols; } - static UnorderedSymbolSet GatherSymbolsFromExpression( - const parser::Expr &expression) { + UnorderedSymbolSet GatherSymbolsFromExpression( + const parser::Expr &expression) const { UnorderedSymbolSet result; - if (const auto *expr{GetExpr(expression)}) { + if (const auto *expr{GetExpr(context_, expression)}) { for (const Symbol &symbol : evaluate::CollectSymbols(*expr)) { result.insert(ResolveAssociations(symbol)); } @@ -1022,7 +1022,7 @@ void DoForallChecker::Enter(const parser::Expr &parsedExpr) { ++exprDepth_; } void DoForallChecker::Leave(const parser::Expr &parsedExpr) { CHECK(exprDepth_ > 0); if (--exprDepth_ == 0) { // Only check top level expressions - if (const SomeExpr * expr{GetExpr(parsedExpr)}) { + if (const SomeExpr * expr{GetExpr(context_, parsedExpr)}) { ActualArgumentSet argSet{CollectActualArguments(*expr)}; for (const evaluate::ActualArgumentRef &argRef : argSet) { CheckIfArgIsDoVar(*argRef, parsedExpr.source, context_); diff --git a/flang/lib/Semantics/check-io.cpp b/flang/lib/Semantics/check-io.cpp index 2bb6678e6e17..470ede163cfb 100644 --- a/flang/lib/Semantics/check-io.cpp +++ b/flang/lib/Semantics/check-io.cpp @@ -209,7 +209,7 @@ void IoChecker::Enter(const parser::Format &spec) { [&](const parser::Label &) { flags_.set(Flag::LabelFmt); }, [&](const parser::Star &) { flags_.set(Flag::StarFmt); }, [&](const parser::Expr &format) { - const SomeExpr *expr{GetExpr(format)}; + const SomeExpr *expr{GetExpr(context_, format)}; if (!expr) { return; } @@ -299,7 +299,7 @@ void IoChecker::Enter(const parser::IdExpr &) { SetSpecifier(IoSpecKind::Id); } void IoChecker::Enter(const parser::IdVariable &spec) { SetSpecifier(IoSpecKind::Id); - const auto *expr{GetExpr(spec)}; + const auto *expr{GetExpr(context_, spec)}; if (!expr || !expr->GetType()) { return; } @@ -546,7 +546,7 @@ void IoChecker::Enter(const parser::IoUnit &spec) { if (stmt_ == IoStmtKind::Write) { CheckForDefinableVariable(*var, "Internal file"); } - if (const auto *expr{GetExpr(*var)}) { + if (const auto *expr{GetExpr(context_, *var)}) { if (HasVectorSubscript(*expr)) { context_.Say(parser::FindSourceLocation(*var), // C1201 "Internal file must not have a vector subscript"_err_en_US); @@ -577,7 +577,7 @@ void IoChecker::Enter(const parser::MsgVariable &var) { void IoChecker::Enter(const parser::OutputItem &item) { flags_.set(Flag::DataList); if (const auto *x{std::get_if(&item.u)}) { - if (const auto *expr{GetExpr(*x)}) { + 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); diff --git a/flang/lib/Semantics/check-io.h b/flang/lib/Semantics/check-io.h index 3e40a14415bd..c23652a2a547 100644 --- a/flang/lib/Semantics/check-io.h +++ b/flang/lib/Semantics/check-io.h @@ -87,7 +87,7 @@ private: template std::optional GetConstExpr(const T &x) { using DefaultCharConstantType = evaluate::Ascii; - if (const SomeExpr * expr{GetExpr(x)}) { + if (const SomeExpr * expr{GetExpr(context_, x)}) { const auto foldExpr{ evaluate::Fold(context_.foldingContext(), common::Clone(*expr))}; if constexpr (std::is_same_v) { diff --git a/flang/lib/Semantics/check-nullify.cpp b/flang/lib/Semantics/check-nullify.cpp index 4c6e78e7f7e3..9ee45541fd5c 100644 --- a/flang/lib/Semantics/check-nullify.cpp +++ b/flang/lib/Semantics/check-nullify.cpp @@ -40,7 +40,7 @@ void NullifyChecker::Leave(const parser::NullifyStmt &nullifyStmt) { } }, [&](const parser::StructureComponent &structureComponent) { - if (const auto *checkedExpr{GetExpr(pointerObject)}) { + if (const auto *checkedExpr{GetExpr(context_, pointerObject)}) { if (!IsPointer(*structureComponent.component.symbol)) { // C951 messages.Say(structureComponent.component.source, "component in NULLIFY statement must have the POINTER attribute"_err_en_US); diff --git a/flang/lib/Semantics/check-omp-structure.cpp b/flang/lib/Semantics/check-omp-structure.cpp index d06a79b834f3..926cf35bb748 100644 --- a/flang/lib/Semantics/check-omp-structure.cpp +++ b/flang/lib/Semantics/check-omp-structure.cpp @@ -50,8 +50,8 @@ public: bool Pre(const parser::AssignmentStmt &assignment) { const auto &var{std::get(assignment.t)}; const auto &expr{std::get(assignment.t)}; - const auto *lhs{GetExpr(var)}; - const auto *rhs{GetExpr(expr)}; + const auto *lhs{GetExpr(context_, var)}; + const auto *rhs{GetExpr(context_, expr)}; if (lhs && rhs) { Tristate isDefined{semantics::IsDefinedAssignment( lhs->GetType(), lhs->Rank(), rhs->GetType(), rhs->Rank())}; @@ -65,7 +65,7 @@ public: } bool Pre(const parser::Expr &expr) { - if (const auto *e{GetExpr(expr)}) { + if (const auto *e{GetExpr(context_, expr)}) { for (const Symbol &symbol : evaluate::CollectSymbols(*e)) { const Symbol &root{GetAssociationRoot(symbol)}; if (IsFunction(root) && @@ -1467,7 +1467,7 @@ void OmpStructureChecker::CheckAtomicUpdateAssignmentStmt( if (const auto *name = std::get_if(&dataRef->u)) { const auto &varSymbol = *name->symbol; - if (const auto *e{GetExpr(expr)}) { + if (const auto *e{GetExpr(context_, expr)}) { for (const Symbol &symbol : evaluate::CollectSymbols(*e)) { if (symbol == varSymbol) { diff --git a/flang/lib/Semantics/check-stop.cpp b/flang/lib/Semantics/check-stop.cpp index ce3482a072bb..43535b07f029 100644 --- a/flang/lib/Semantics/check-stop.cpp +++ b/flang/lib/Semantics/check-stop.cpp @@ -18,7 +18,7 @@ namespace Fortran::semantics { void StopChecker::Enter(const parser::StopStmt &stmt) { const auto &stopCode{std::get>(stmt.t)}; - if (const auto *expr{GetExpr(stopCode)}) { + if (const auto *expr{GetExpr(context_, stopCode)}) { const parser::CharBlock &source{parser::FindSourceLocation(stopCode)}; if (ExprHasTypeCategory(*expr, common::TypeCategory::Integer)) { // C1171 default kind diff --git a/flang/lib/Semantics/data-to-inits.cpp b/flang/lib/Semantics/data-to-inits.cpp index 6bdbf5f6549f..d41de5ad234e 100644 --- a/flang/lib/Semantics/data-to-inits.cpp +++ b/flang/lib/Semantics/data-to-inits.cpp @@ -36,13 +36,13 @@ namespace Fortran::semantics { // repetition. template class ValueListIterator { public: - explicit ValueListIterator(const std::list &list) - : end_{list.end()}, at_{list.begin()} { + ValueListIterator(SemanticsContext &context, const std::list &list) + : context_{context}, end_{list.end()}, at_{list.begin()} { SetRepetitionCount(); } bool hasFatalError() const { return hasFatalError_; } bool IsAtEnd() const { return at_ == end_; } - const SomeExpr *operator*() const { return GetExpr(GetConstant()); } + const SomeExpr *operator*() const { return GetExpr(context_, GetConstant()); } parser::CharBlock LocateSource() const { return GetConstant().source; } ValueListIterator &operator++() { if (repetitionsRemaining_ > 0) { @@ -64,6 +64,7 @@ private: return std::get(GetValue().t); } + SemanticsContext &context_; listIterator end_, at_; ConstantSubscript repetitionsRemaining_{0}; bool hasFatalError_{false}; @@ -93,7 +94,7 @@ class DataInitializationCompiler { public: DataInitializationCompiler(DataInitializations &inits, evaluate::ExpressionAnalyzer &a, const std::list &list) - : inits_{inits}, exprAnalyzer_{a}, values_{list} {} + : inits_{inits}, exprAnalyzer_{a}, values_{a.context(), list} {} const DataInitializations &inits() const { return inits_; } bool HasSurplusValues() const { return !values_.IsAtEnd(); } bool Scan(const parser::DataStmtObject &); @@ -134,7 +135,7 @@ bool DataInitializationCompiler::Scan( template bool DataInitializationCompiler::Scan(const parser::Variable &var) { - if (const auto *expr{GetExpr(var)}) { + if (const auto *expr{GetExpr(exprAnalyzer_.context(), var)}) { exprAnalyzer_.GetFoldingContext().messages().SetLocation(var.GetSource()); if (InitDesignator(*expr)) { return true; @@ -160,10 +161,13 @@ template bool DataInitializationCompiler::Scan(const parser::DataImpliedDo &ido) { const auto &bounds{std::get(ido.t)}; auto name{bounds.name.thing.thing}; - const auto *lowerExpr{GetExpr(bounds.lower.thing.thing)}; - const auto *upperExpr{GetExpr(bounds.upper.thing.thing)}; - const auto *stepExpr{ - bounds.step ? GetExpr(bounds.step->thing.thing) : nullptr}; + const auto *lowerExpr{ + GetExpr(exprAnalyzer_.context(), bounds.lower.thing.thing)}; + const auto *upperExpr{ + GetExpr(exprAnalyzer_.context(), bounds.upper.thing.thing)}; + const auto *stepExpr{bounds.step + ? GetExpr(exprAnalyzer_.context(), bounds.step->thing.thing) + : nullptr}; if (lowerExpr && upperExpr) { // Fold the bounds expressions (again) in case any of them depend // on outer implied DO loops. diff --git a/flang/lib/Semantics/resolve-directives.cpp b/flang/lib/Semantics/resolve-directives.cpp index 068f944962eb..2af91517a43d 100644 --- a/flang/lib/Semantics/resolve-directives.cpp +++ b/flang/lib/Semantics/resolve-directives.cpp @@ -279,6 +279,18 @@ public: return true; } + bool Pre(const parser::StmtFunctionStmt &x) { + const auto &parsedExpr{std::get>(x.t)}; + if (const auto *expr{GetExpr(context_, parsedExpr)}) { + for (const Symbol &symbol : evaluate::CollectSymbols(*expr)) { + if (!IsStmtFunctionDummy(symbol)) { + stmtFunctionExprSymbols_.insert(symbol.GetUltimate()); + } + } + } + return true; + } + bool Pre(const parser::OpenMPBlockConstruct &); void Post(const parser::OpenMPBlockConstruct &); diff --git a/flang/lib/Semantics/tools.cpp b/flang/lib/Semantics/tools.cpp index e0cb86ed8160..a1b4ea9ec0e2 100644 --- a/flang/lib/Semantics/tools.cpp +++ b/flang/lib/Semantics/tools.cpp @@ -385,8 +385,9 @@ bool ExprTypeKindIsDefault( // If an analyzed expr or assignment is missing, dump the node and die. template -static void CheckMissingAnalysis(bool absent, const T &x) { - if (absent) { +static void CheckMissingAnalysis( + bool crash, SemanticsContext *context, const T &x) { + if (crash && !(context && context->AnyFatalError())) { std::string buf; llvm::raw_string_ostream ss{buf}; ss << "node has not been analyzed:\n"; @@ -395,34 +396,35 @@ static void CheckMissingAnalysis(bool absent, const T &x) { } } -template static const SomeExpr *GetTypedExpr(const T &x) { - CheckMissingAnalysis(!x.typedExpr, x); - return common::GetPtrFromOptional(x.typedExpr->v); -} const SomeExpr *GetExprHelper::Get(const parser::Expr &x) { - return GetTypedExpr(x); + CheckMissingAnalysis(crashIfNoExpr_ && !x.typedExpr, context_, x); + return x.typedExpr ? common::GetPtrFromOptional(x.typedExpr->v) : nullptr; } const SomeExpr *GetExprHelper::Get(const parser::Variable &x) { - return GetTypedExpr(x); + CheckMissingAnalysis(crashIfNoExpr_ && !x.typedExpr, context_, x); + return x.typedExpr ? common::GetPtrFromOptional(x.typedExpr->v) : nullptr; } const SomeExpr *GetExprHelper::Get(const parser::DataStmtConstant &x) { - return GetTypedExpr(x); + CheckMissingAnalysis(crashIfNoExpr_ && !x.typedExpr, context_, x); + return x.typedExpr ? common::GetPtrFromOptional(x.typedExpr->v) : nullptr; } const SomeExpr *GetExprHelper::Get(const parser::AllocateObject &x) { - return GetTypedExpr(x); + CheckMissingAnalysis(crashIfNoExpr_ && !x.typedExpr, context_, x); + return x.typedExpr ? common::GetPtrFromOptional(x.typedExpr->v) : nullptr; } const SomeExpr *GetExprHelper::Get(const parser::PointerObject &x) { - return GetTypedExpr(x); + CheckMissingAnalysis(crashIfNoExpr_ && !x.typedExpr, context_, x); + return x.typedExpr ? common::GetPtrFromOptional(x.typedExpr->v) : nullptr; } const evaluate::Assignment *GetAssignment(const parser::AssignmentStmt &x) { - CheckMissingAnalysis(!x.typedAssignment, x); - return common::GetPtrFromOptional(x.typedAssignment->v); + return x.typedAssignment ? common::GetPtrFromOptional(x.typedAssignment->v) + : nullptr; } const evaluate::Assignment *GetAssignment( const parser::PointerAssignmentStmt &x) { - CheckMissingAnalysis(!x.typedAssignment, x); - return common::GetPtrFromOptional(x.typedAssignment->v); + return x.typedAssignment ? common::GetPtrFromOptional(x.typedAssignment->v) + : nullptr; } const Symbol *FindInterface(const Symbol &symbol) { @@ -998,7 +1000,7 @@ parser::CharBlock GetImageControlStmtLocation( } bool HasCoarray(const parser::Expr &expression) { - if (const auto *expr{GetExpr(expression)}) { + if (const auto *expr{GetExpr(nullptr, expression)}) { for (const Symbol &symbol : evaluate::CollectSymbols(*expr)) { if (evaluate::IsCoarray(symbol)) { return true;