1 //===-- lib/Evaluate/check-expression.cpp ---------------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "flang/Evaluate/check-expression.h" 10 #include "flang/Evaluate/characteristics.h" 11 #include "flang/Evaluate/intrinsics.h" 12 #include "flang/Evaluate/traverse.h" 13 #include "flang/Evaluate/type.h" 14 #include "flang/Semantics/symbol.h" 15 #include "flang/Semantics/tools.h" 16 #include <set> 17 #include <string> 18 19 namespace Fortran::evaluate { 20 21 // Constant expression predicate IsConstantExpr(). 22 // This code determines whether an expression is a "constant expression" 23 // in the sense of section 10.1.12. This is not the same thing as being 24 // able to fold it (yet) into a known constant value; specifically, 25 // the expression may reference derived type kind parameters whose values 26 // are not yet known. 27 class IsConstantExprHelper : public AllTraverse<IsConstantExprHelper, true> { 28 public: 29 using Base = AllTraverse<IsConstantExprHelper, true>; 30 IsConstantExprHelper() : Base{*this} {} 31 using Base::operator(); 32 33 bool operator()(const TypeParamInquiry &inq) const { 34 return semantics::IsKindTypeParameter(inq.parameter()); 35 } 36 bool operator()(const semantics::Symbol &symbol) const { 37 const auto &ultimate{symbol.GetUltimate()}; 38 return IsNamedConstant(ultimate) || IsImpliedDoIndex(ultimate) || 39 IsInitialProcedureTarget(ultimate); 40 } 41 bool operator()(const CoarrayRef &) const { return false; } 42 bool operator()(const semantics::ParamValue ¶m) const { 43 return param.isExplicit() && (*this)(param.GetExplicit()); 44 } 45 template <typename T> bool operator()(const FunctionRef<T> &call) const { 46 if (const auto *intrinsic{std::get_if<SpecificIntrinsic>(&call.proc().u)}) { 47 // kind is always a constant, and we avoid cascading errors by calling 48 // invalid calls to intrinsics constant 49 return intrinsic->name == "kind" || 50 intrinsic->name == IntrinsicProcTable::InvalidName; 51 // TODO: other inquiry intrinsics 52 } else { 53 return false; 54 } 55 } 56 bool operator()(const StructureConstructor &constructor) const { 57 for (const auto &[symRef, expr] : constructor) { 58 if (!IsConstantStructureConstructorComponent(*symRef, expr.value())) { 59 return false; 60 } 61 } 62 return true; 63 } 64 bool operator()(const Component &component) const { 65 return (*this)(component.base()); 66 } 67 // Forbid integer division by zero in constants. 68 template <int KIND> 69 bool operator()( 70 const Divide<Type<TypeCategory::Integer, KIND>> &division) const { 71 using T = Type<TypeCategory::Integer, KIND>; 72 if (const auto divisor{GetScalarConstantValue<T>(division.right())}) { 73 return !divisor->IsZero() && (*this)(division.left()); 74 } else { 75 return false; 76 } 77 } 78 79 bool operator()(const Constant<SomeDerived> &) const { return true; } 80 81 private: 82 bool IsConstantStructureConstructorComponent( 83 const Symbol &component, const Expr<SomeType> &expr) const { 84 if (IsAllocatable(component)) { 85 return IsNullPointer(expr); 86 } else if (IsPointer(component)) { 87 return IsNullPointer(expr) || IsInitialDataTarget(expr) || 88 IsInitialProcedureTarget(expr); 89 } else { 90 return (*this)(expr); 91 } 92 } 93 }; 94 95 template <typename A> bool IsConstantExpr(const A &x) { 96 return IsConstantExprHelper{}(x); 97 } 98 template bool IsConstantExpr(const Expr<SomeType> &); 99 template bool IsConstantExpr(const Expr<SomeInteger> &); 100 template bool IsConstantExpr(const Expr<SubscriptInteger> &); 101 template bool IsConstantExpr(const StructureConstructor &); 102 103 // IsActuallyConstant() 104 struct IsActuallyConstantHelper { 105 template <typename A> bool operator()(const A &) { return false; } 106 template <typename T> bool operator()(const Constant<T> &) { return true; } 107 template <typename T> bool operator()(const Parentheses<T> &x) { 108 return (*this)(x.left()); 109 } 110 template <typename T> bool operator()(const Expr<T> &x) { 111 return std::visit([=](const auto &y) { return (*this)(y); }, x.u); 112 } 113 template <typename A> bool operator()(const A *x) { return x && (*this)(*x); } 114 template <typename A> bool operator()(const std::optional<A> &x) { 115 return x && (*this)(*x); 116 } 117 }; 118 119 template <typename A> bool IsActuallyConstant(const A &x) { 120 return IsActuallyConstantHelper{}(x); 121 } 122 123 template bool IsActuallyConstant(const Expr<SomeType> &); 124 125 // Object pointer initialization checking predicate IsInitialDataTarget(). 126 // This code determines whether an expression is allowable as the static 127 // data address used to initialize a pointer with "=> x". See C765. 128 class IsInitialDataTargetHelper 129 : public AllTraverse<IsInitialDataTargetHelper, true> { 130 public: 131 using Base = AllTraverse<IsInitialDataTargetHelper, true>; 132 using Base::operator(); 133 explicit IsInitialDataTargetHelper(parser::ContextualMessages *m) 134 : Base{*this}, messages_{m} {} 135 136 bool emittedMessage() const { return emittedMessage_; } 137 138 bool operator()(const BOZLiteralConstant &) const { return false; } 139 bool operator()(const NullPointer &) const { return true; } 140 template <typename T> bool operator()(const Constant<T> &) const { 141 return false; 142 } 143 bool operator()(const semantics::Symbol &symbol) { 144 const Symbol &ultimate{symbol.GetUltimate()}; 145 if (IsAllocatable(ultimate)) { 146 if (messages_) { 147 messages_->Say( 148 "An initial data target may not be a reference to an ALLOCATABLE '%s'"_err_en_US, 149 ultimate.name()); 150 emittedMessage_ = true; 151 } 152 return false; 153 } else if (ultimate.Corank() > 0) { 154 if (messages_) { 155 messages_->Say( 156 "An initial data target may not be a reference to a coarray '%s'"_err_en_US, 157 ultimate.name()); 158 emittedMessage_ = true; 159 } 160 return false; 161 } else if (!ultimate.attrs().test(semantics::Attr::TARGET)) { 162 if (messages_) { 163 messages_->Say( 164 "An initial data target may not be a reference to an object '%s' that lacks the TARGET attribute"_err_en_US, 165 ultimate.name()); 166 emittedMessage_ = true; 167 } 168 return false; 169 } else if (!IsSaved(ultimate)) { 170 if (messages_) { 171 messages_->Say( 172 "An initial data target may not be a reference to an object '%s' that lacks the SAVE attribute"_err_en_US, 173 ultimate.name()); 174 emittedMessage_ = true; 175 } 176 return false; 177 } 178 return true; 179 } 180 bool operator()(const StaticDataObject &) const { return false; } 181 bool operator()(const TypeParamInquiry &) const { return false; } 182 bool operator()(const Triplet &x) const { 183 return IsConstantExpr(x.lower()) && IsConstantExpr(x.upper()) && 184 IsConstantExpr(x.stride()); 185 } 186 bool operator()(const Subscript &x) const { 187 return std::visit(common::visitors{ 188 [&](const Triplet &t) { return (*this)(t); }, 189 [&](const auto &y) { 190 return y.value().Rank() == 0 && 191 IsConstantExpr(y.value()); 192 }, 193 }, 194 x.u); 195 } 196 bool operator()(const CoarrayRef &) const { return false; } 197 bool operator()(const Substring &x) const { 198 return IsConstantExpr(x.lower()) && IsConstantExpr(x.upper()) && 199 (*this)(x.parent()); 200 } 201 bool operator()(const DescriptorInquiry &) const { return false; } 202 template <typename T> bool operator()(const ArrayConstructor<T> &) const { 203 return false; 204 } 205 bool operator()(const StructureConstructor &) const { return false; } 206 template <typename T> bool operator()(const FunctionRef<T> &) { 207 return false; 208 } 209 template <typename D, typename R, typename... O> 210 bool operator()(const Operation<D, R, O...> &) const { 211 return false; 212 } 213 template <typename T> bool operator()(const Parentheses<T> &x) const { 214 return (*this)(x.left()); 215 } 216 template <typename T> bool operator()(const FunctionRef<T> &x) const { 217 return false; 218 } 219 bool operator()(const Relational<SomeType> &) const { return false; } 220 221 private: 222 parser::ContextualMessages *messages_; 223 bool emittedMessage_{false}; 224 }; 225 226 bool IsInitialDataTarget( 227 const Expr<SomeType> &x, parser::ContextualMessages *messages) { 228 IsInitialDataTargetHelper helper{messages}; 229 bool result{helper(x)}; 230 if (!result && messages && !helper.emittedMessage()) { 231 messages->Say( 232 "An initial data target must be a designator with constant subscripts"_err_en_US); 233 } 234 return result; 235 } 236 237 bool IsInitialProcedureTarget(const semantics::Symbol &symbol) { 238 const auto &ultimate{symbol.GetUltimate()}; 239 return std::visit( 240 common::visitors{ 241 [](const semantics::SubprogramDetails &) { return true; }, 242 [](const semantics::SubprogramNameDetails &) { return true; }, 243 [&](const semantics::ProcEntityDetails &proc) { 244 return !semantics::IsPointer(ultimate) && !proc.isDummy(); 245 }, 246 [](const auto &) { return false; }, 247 }, 248 ultimate.details()); 249 } 250 251 bool IsInitialProcedureTarget(const ProcedureDesignator &proc) { 252 if (const auto *intrin{proc.GetSpecificIntrinsic()}) { 253 return !intrin->isRestrictedSpecific; 254 } else if (proc.GetComponent()) { 255 return false; 256 } else { 257 return IsInitialProcedureTarget(DEREF(proc.GetSymbol())); 258 } 259 } 260 261 bool IsInitialProcedureTarget(const Expr<SomeType> &expr) { 262 if (const auto *proc{std::get_if<ProcedureDesignator>(&expr.u)}) { 263 return IsInitialProcedureTarget(*proc); 264 } else { 265 return IsNullPointer(expr); 266 } 267 } 268 269 class ScalarExpansionVisitor : public AnyTraverse<ScalarExpansionVisitor, 270 std::optional<Expr<SomeType>>> { 271 public: 272 using Result = std::optional<Expr<SomeType>>; 273 using Base = AnyTraverse<ScalarExpansionVisitor, Result>; 274 ScalarExpansionVisitor( 275 ConstantSubscripts &&shape, std::optional<ConstantSubscripts> &&lb) 276 : Base{*this}, shape_{std::move(shape)}, lbounds_{std::move(lb)} {} 277 using Base::operator(); 278 template <typename T> Result operator()(const Constant<T> &x) { 279 auto expanded{x.Reshape(std::move(shape_))}; 280 if (lbounds_) { 281 expanded.set_lbounds(std::move(*lbounds_)); 282 } 283 return AsGenericExpr(std::move(expanded)); 284 } 285 286 private: 287 ConstantSubscripts shape_; 288 std::optional<ConstantSubscripts> lbounds_; 289 }; 290 291 // Converts, folds, and then checks type, rank, and shape of an 292 // initialization expression for a named constant, a non-pointer 293 // variable static initializatio, a component default initializer, 294 // a type parameter default value, or instantiated type parameter value. 295 std::optional<Expr<SomeType>> NonPointerInitializationExpr(const Symbol &symbol, 296 Expr<SomeType> &&x, FoldingContext &context, 297 const semantics::Scope *instantiation) { 298 CHECK(!IsPointer(symbol)); 299 if (auto symTS{ 300 characteristics::TypeAndShape::Characterize(symbol, context)}) { 301 auto xType{x.GetType()}; 302 if (auto converted{ConvertToType(symTS->type(), std::move(x))}) { 303 auto folded{Fold(context, std::move(*converted))}; 304 if (IsActuallyConstant(folded)) { 305 int symRank{GetRank(symTS->shape())}; 306 if (IsImpliedShape(symbol)) { 307 if (folded.Rank() == symRank) { 308 return {std::move(folded)}; 309 } else { 310 context.messages().Say( 311 "Implied-shape parameter '%s' has rank %d but its initializer has rank %d"_err_en_US, 312 symbol.name(), symRank, folded.Rank()); 313 } 314 } else if (auto extents{AsConstantExtents(context, symTS->shape())}) { 315 if (folded.Rank() == 0 && symRank > 0) { 316 return ScalarConstantExpander{std::move(*extents), 317 AsConstantExtents( 318 context, GetLowerBounds(context, NamedEntity{symbol}))} 319 .Expand(std::move(folded)); 320 } else if (auto resultShape{GetShape(context, folded)}) { 321 if (CheckConformance(context.messages(), symTS->shape(), 322 *resultShape, "initialized object", 323 "initialization expression", false, false)) { 324 return {std::move(folded)}; 325 } 326 } 327 } else if (IsNamedConstant(symbol)) { 328 if (IsExplicitShape(symbol)) { 329 context.messages().Say( 330 "Named constant '%s' array must have constant shape"_err_en_US, 331 symbol.name()); 332 } else { 333 // Declaration checking handles other cases 334 } 335 } else { 336 context.messages().Say( 337 "Shape of initialized object '%s' must be constant"_err_en_US, 338 symbol.name()); 339 } 340 } else if (IsErrorExpr(folded)) { 341 } else if (IsLenTypeParameter(symbol)) { 342 return {std::move(folded)}; 343 } else if (IsKindTypeParameter(symbol)) { 344 if (instantiation) { 345 context.messages().Say( 346 "Value of kind type parameter '%s' (%s) must be a scalar INTEGER constant"_err_en_US, 347 symbol.name(), folded.AsFortran()); 348 } else { 349 return {std::move(folded)}; 350 } 351 } else if (IsNamedConstant(symbol)) { 352 context.messages().Say( 353 "Value of named constant '%s' (%s) cannot be computed as a constant value"_err_en_US, 354 symbol.name(), folded.AsFortran()); 355 } else { 356 context.messages().Say( 357 "Initialization expression for '%s' (%s) cannot be computed as a constant value"_err_en_US, 358 symbol.name(), folded.AsFortran()); 359 } 360 } else if (xType) { 361 context.messages().Say( 362 "Initialization expression cannot be converted to declared type of '%s' from %s"_err_en_US, 363 symbol.name(), xType->AsFortran()); 364 } else { 365 context.messages().Say( 366 "Initialization expression cannot be converted to declared type of '%s'"_err_en_US, 367 symbol.name()); 368 } 369 } 370 return std::nullopt; 371 } 372 373 // Specification expression validation (10.1.11(2), C1010) 374 class CheckSpecificationExprHelper 375 : public AnyTraverse<CheckSpecificationExprHelper, 376 std::optional<std::string>> { 377 public: 378 using Result = std::optional<std::string>; 379 using Base = AnyTraverse<CheckSpecificationExprHelper, Result>; 380 explicit CheckSpecificationExprHelper( 381 const semantics::Scope &s, FoldingContext &context) 382 : Base{*this}, scope_{s}, context_{context} {} 383 using Base::operator(); 384 385 Result operator()(const ProcedureDesignator &) const { 386 return "dummy procedure argument"; 387 } 388 Result operator()(const CoarrayRef &) const { return "coindexed reference"; } 389 390 Result operator()(const semantics::Symbol &symbol) const { 391 const auto &ultimate{symbol.GetUltimate()}; 392 if (semantics::IsNamedConstant(ultimate) || ultimate.owner().IsModule() || 393 ultimate.owner().IsSubmodule()) { 394 return std::nullopt; 395 } else if (scope_.IsDerivedType() && 396 IsVariableName(ultimate)) { // C750, C754 397 return "derived type component or type parameter value not allowed to " 398 "reference variable '"s + 399 ultimate.name().ToString() + "'"; 400 } else if (IsDummy(ultimate)) { 401 if (ultimate.attrs().test(semantics::Attr::OPTIONAL)) { 402 return "reference to OPTIONAL dummy argument '"s + 403 ultimate.name().ToString() + "'"; 404 } else if (ultimate.attrs().test(semantics::Attr::INTENT_OUT)) { 405 return "reference to INTENT(OUT) dummy argument '"s + 406 ultimate.name().ToString() + "'"; 407 } else if (ultimate.has<semantics::ObjectEntityDetails>()) { 408 return std::nullopt; 409 } else { 410 return "dummy procedure argument"; 411 } 412 } else if (const auto *object{ 413 ultimate.detailsIf<semantics::ObjectEntityDetails>()}) { 414 if (object->commonBlock()) { 415 return std::nullopt; 416 } 417 } 418 for (const semantics::Scope *s{&scope_}; !s->IsGlobal();) { 419 s = &s->parent(); 420 if (s == &ultimate.owner()) { 421 return std::nullopt; 422 } 423 } 424 return "reference to local entity '"s + ultimate.name().ToString() + "'"; 425 } 426 427 Result operator()(const Component &x) const { 428 // Don't look at the component symbol. 429 return (*this)(x.base()); 430 } 431 Result operator()(const DescriptorInquiry &) const { 432 // Subtle: Uses of SIZE(), LBOUND(), &c. that are valid in specification 433 // expressions will have been converted to expressions over descriptor 434 // inquiries by Fold(). 435 return std::nullopt; 436 } 437 438 Result operator()(const TypeParamInquiry &inq) const { 439 if (scope_.IsDerivedType() && !IsConstantExpr(inq) && 440 inq.base() /* X%T, not local T */) { // C750, C754 441 return "non-constant reference to a type parameter inquiry not " 442 "allowed for derived type components or type parameter values"; 443 } 444 return std::nullopt; 445 } 446 447 template <typename T> Result operator()(const FunctionRef<T> &x) const { 448 if (const auto *symbol{x.proc().GetSymbol()}) { 449 if (!semantics::IsPureProcedure(*symbol)) { 450 return "reference to impure function '"s + symbol->name().ToString() + 451 "'"; 452 } 453 if (semantics::IsStmtFunction(*symbol)) { 454 return "reference to statement function '"s + 455 symbol->name().ToString() + "'"; 456 } 457 if (scope_.IsDerivedType()) { // C750, C754 458 return "reference to function '"s + symbol->name().ToString() + 459 "' not allowed for derived type components or type parameter" 460 " values"; 461 } 462 // TODO: other checks for standard module procedures 463 } else { 464 const SpecificIntrinsic &intrin{DEREF(x.proc().GetSpecificIntrinsic())}; 465 if (scope_.IsDerivedType()) { // C750, C754 466 if ((context_.intrinsics().IsIntrinsic(intrin.name) && 467 badIntrinsicsForComponents_.find(intrin.name) != 468 badIntrinsicsForComponents_.end()) || 469 IsProhibitedFunction(intrin.name)) { 470 return "reference to intrinsic '"s + intrin.name + 471 "' not allowed for derived type components or type parameter" 472 " values"; 473 } 474 if (context_.intrinsics().GetIntrinsicClass(intrin.name) == 475 IntrinsicClass::inquiryFunction && 476 !IsConstantExpr(x)) { 477 return "non-constant reference to inquiry intrinsic '"s + 478 intrin.name + 479 "' not allowed for derived type components or type" 480 " parameter values"; 481 } 482 } else if (intrin.name == "present") { 483 return std::nullopt; // no need to check argument(s) 484 } 485 if (IsConstantExpr(x)) { 486 // inquiry functions may not need to check argument(s) 487 return std::nullopt; 488 } 489 } 490 return (*this)(x.arguments()); 491 } 492 493 private: 494 const semantics::Scope &scope_; 495 FoldingContext &context_; 496 const std::set<std::string> badIntrinsicsForComponents_{ 497 "allocated", "associated", "extends_type_of", "present", "same_type_as"}; 498 static bool IsProhibitedFunction(std::string name) { return false; } 499 }; 500 501 template <typename A> 502 void CheckSpecificationExpr( 503 const A &x, const semantics::Scope &scope, FoldingContext &context) { 504 if (auto why{CheckSpecificationExprHelper{scope, context}(x)}) { 505 context.messages().Say( 506 "Invalid specification expression: %s"_err_en_US, *why); 507 } 508 } 509 510 template void CheckSpecificationExpr( 511 const Expr<SomeType> &, const semantics::Scope &, FoldingContext &); 512 template void CheckSpecificationExpr( 513 const Expr<SomeInteger> &, const semantics::Scope &, FoldingContext &); 514 template void CheckSpecificationExpr( 515 const Expr<SubscriptInteger> &, const semantics::Scope &, FoldingContext &); 516 template void CheckSpecificationExpr(const std::optional<Expr<SomeType>> &, 517 const semantics::Scope &, FoldingContext &); 518 template void CheckSpecificationExpr(const std::optional<Expr<SomeInteger>> &, 519 const semantics::Scope &, FoldingContext &); 520 template void CheckSpecificationExpr( 521 const std::optional<Expr<SubscriptInteger>> &, const semantics::Scope &, 522 FoldingContext &); 523 524 // IsSimplyContiguous() -- 9.5.4 525 class IsSimplyContiguousHelper 526 : public AnyTraverse<IsSimplyContiguousHelper, std::optional<bool>> { 527 public: 528 using Result = std::optional<bool>; // tri-state 529 using Base = AnyTraverse<IsSimplyContiguousHelper, Result>; 530 explicit IsSimplyContiguousHelper(FoldingContext &c) 531 : Base{*this}, context_{c} {} 532 using Base::operator(); 533 534 Result operator()(const semantics::Symbol &symbol) const { 535 if (symbol.attrs().test(semantics::Attr::CONTIGUOUS) || 536 symbol.Rank() == 0) { 537 return true; 538 } else if (semantics::IsPointer(symbol)) { 539 return false; 540 } else if (const auto *details{ 541 symbol.detailsIf<semantics::ObjectEntityDetails>()}) { 542 // N.B. ALLOCATABLEs are deferred shape, not assumed, and 543 // are obviously contiguous. 544 return !details->IsAssumedShape() && !details->IsAssumedRank(); 545 } else { 546 return false; 547 } 548 } 549 550 Result operator()(const ArrayRef &x) const { 551 const auto &symbol{x.GetLastSymbol()}; 552 if (!(*this)(symbol)) { 553 return false; 554 } else if (auto rank{CheckSubscripts(x.subscript())}) { 555 // a(:)%b(1,1) is not contiguous; a(1)%b(:,:) is 556 return *rank > 0 || x.Rank() == 0; 557 } else { 558 return false; 559 } 560 } 561 Result operator()(const CoarrayRef &x) const { 562 return CheckSubscripts(x.subscript()).has_value(); 563 } 564 Result operator()(const Component &x) const { 565 return x.base().Rank() == 0 && (*this)(x.GetLastSymbol()); 566 } 567 Result operator()(const ComplexPart &) const { return false; } 568 Result operator()(const Substring &) const { return false; } 569 570 template <typename T> Result operator()(const FunctionRef<T> &x) const { 571 if (auto chars{ 572 characteristics::Procedure::Characterize(x.proc(), context_)}) { 573 if (chars->functionResult) { 574 const auto &result{*chars->functionResult}; 575 return !result.IsProcedurePointer() && 576 result.attrs.test(characteristics::FunctionResult::Attr::Pointer) && 577 result.attrs.test( 578 characteristics::FunctionResult::Attr::Contiguous); 579 } 580 } 581 return false; 582 } 583 584 private: 585 // If the subscripts can possibly be on a simply-contiguous array reference, 586 // return the rank. 587 static std::optional<int> CheckSubscripts( 588 const std::vector<Subscript> &subscript) { 589 bool anyTriplet{false}; 590 int rank{0}; 591 for (auto j{subscript.size()}; j-- > 0;) { 592 if (const auto *triplet{std::get_if<Triplet>(&subscript[j].u)}) { 593 if (!triplet->IsStrideOne()) { 594 return std::nullopt; 595 } else if (anyTriplet) { 596 if (triplet->lower() || triplet->upper()) { 597 // all triplets before the last one must be just ":" 598 return std::nullopt; 599 } 600 } else { 601 anyTriplet = true; 602 } 603 ++rank; 604 } else if (anyTriplet || subscript[j].Rank() > 0) { 605 return std::nullopt; 606 } 607 } 608 return rank; 609 } 610 611 FoldingContext &context_; 612 }; 613 614 template <typename A> 615 bool IsSimplyContiguous(const A &x, FoldingContext &context) { 616 if (IsVariable(x)) { 617 auto known{IsSimplyContiguousHelper{context}(x)}; 618 return known && *known; 619 } else { 620 return true; // not a variable 621 } 622 } 623 624 template bool IsSimplyContiguous(const Expr<SomeType> &, FoldingContext &); 625 626 // IsErrorExpr() 627 struct IsErrorExprHelper : public AnyTraverse<IsErrorExprHelper, bool> { 628 using Result = bool; 629 using Base = AnyTraverse<IsErrorExprHelper, Result>; 630 IsErrorExprHelper() : Base{*this} {} 631 using Base::operator(); 632 633 bool operator()(const SpecificIntrinsic &x) { 634 return x.name == IntrinsicProcTable::InvalidName; 635 } 636 }; 637 638 template <typename A> bool IsErrorExpr(const A &x) { 639 return IsErrorExprHelper{}(x); 640 } 641 642 template bool IsErrorExpr(const Expr<SomeType> &); 643 644 } // namespace Fortran::evaluate 645