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 predicates IsConstantExpr() & IsScopeInvariantExpr(). 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 // 28 // The variant form (IsScopeInvariantExpr()) also accepts symbols that are 29 // INTENT(IN) dummy arguments without the VALUE attribute. 30 template <bool INVARIANT> 31 class IsConstantExprHelper 32 : public AllTraverse<IsConstantExprHelper<INVARIANT>, true> { 33 public: 34 using Base = AllTraverse<IsConstantExprHelper, true>; 35 IsConstantExprHelper() : Base{*this} {} 36 using Base::operator(); 37 38 // A missing expression is not considered to be constant. 39 template <typename A> bool operator()(const std::optional<A> &x) const { 40 return x && (*this)(*x); 41 } 42 43 bool operator()(const TypeParamInquiry &inq) const { 44 return INVARIANT || semantics::IsKindTypeParameter(inq.parameter()); 45 } 46 bool operator()(const semantics::Symbol &symbol) const { 47 const auto &ultimate{GetAssociationRoot(symbol)}; 48 return IsNamedConstant(ultimate) || IsImpliedDoIndex(ultimate) || 49 IsInitialProcedureTarget(ultimate) || 50 ultimate.has<semantics::TypeParamDetails>() || 51 (INVARIANT && IsIntentIn(symbol) && 52 !symbol.attrs().test(semantics::Attr::VALUE)); 53 } 54 bool operator()(const CoarrayRef &) const { return false; } 55 bool operator()(const semantics::ParamValue ¶m) const { 56 return param.isExplicit() && (*this)(param.GetExplicit()); 57 } 58 bool operator()(const ProcedureRef &) const; 59 bool operator()(const StructureConstructor &constructor) const { 60 for (const auto &[symRef, expr] : constructor) { 61 if (!IsConstantStructureConstructorComponent(*symRef, expr.value())) { 62 return false; 63 } 64 } 65 return true; 66 } 67 bool operator()(const Component &component) const { 68 return (*this)(component.base()); 69 } 70 // Forbid integer division by zero in constants. 71 template <int KIND> 72 bool operator()( 73 const Divide<Type<TypeCategory::Integer, KIND>> &division) const { 74 using T = Type<TypeCategory::Integer, KIND>; 75 if (const auto divisor{GetScalarConstantValue<T>(division.right())}) { 76 return !divisor->IsZero() && (*this)(division.left()); 77 } else { 78 return false; 79 } 80 } 81 82 bool operator()(const Constant<SomeDerived> &) const { return true; } 83 bool operator()(const DescriptorInquiry &x) const { 84 const Symbol &sym{x.base().GetLastSymbol()}; 85 return INVARIANT && !IsAllocatable(sym) && 86 (!IsDummy(sym) || 87 (IsIntentIn(sym) && !sym.attrs().test(semantics::Attr::VALUE))); 88 } 89 90 private: 91 bool IsConstantStructureConstructorComponent( 92 const Symbol &, const Expr<SomeType> &) const; 93 bool IsConstantExprShape(const Shape &) const; 94 }; 95 96 template <bool INVARIANT> 97 bool IsConstantExprHelper<INVARIANT>::IsConstantStructureConstructorComponent( 98 const Symbol &component, const Expr<SomeType> &expr) const { 99 if (IsAllocatable(component)) { 100 return IsNullPointer(expr); 101 } else if (IsPointer(component)) { 102 return IsNullPointer(expr) || IsInitialDataTarget(expr) || 103 IsInitialProcedureTarget(expr); 104 } else { 105 return (*this)(expr); 106 } 107 } 108 109 template <bool INVARIANT> 110 bool IsConstantExprHelper<INVARIANT>::operator()( 111 const ProcedureRef &call) const { 112 // LBOUND, UBOUND, and SIZE with DIM= arguments will have been rewritten 113 // into DescriptorInquiry operations. 114 if (const auto *intrinsic{std::get_if<SpecificIntrinsic>(&call.proc().u)}) { 115 if (intrinsic->name == "kind" || 116 intrinsic->name == IntrinsicProcTable::InvalidName) { 117 // kind is always a constant, and we avoid cascading errors by considering 118 // invalid calls to intrinsics to be constant 119 return true; 120 } else if (intrinsic->name == "lbound" && call.arguments().size() == 1) { 121 // LBOUND(x) without DIM= 122 auto base{ExtractNamedEntity(call.arguments()[0]->UnwrapExpr())}; 123 return base && IsConstantExprShape(GetLBOUNDs(*base)); 124 } else if (intrinsic->name == "ubound" && call.arguments().size() == 1) { 125 // UBOUND(x) without DIM= 126 auto base{ExtractNamedEntity(call.arguments()[0]->UnwrapExpr())}; 127 return base && IsConstantExprShape(GetUBOUNDs(*base)); 128 } else if (intrinsic->name == "shape") { 129 auto shape{GetShape(call.arguments()[0]->UnwrapExpr())}; 130 return shape && IsConstantExprShape(*shape); 131 } else if (intrinsic->name == "size" && call.arguments().size() == 1) { 132 // SIZE(x) without DIM 133 auto shape{GetShape(call.arguments()[0]->UnwrapExpr())}; 134 return shape && IsConstantExprShape(*shape); 135 } 136 // TODO: STORAGE_SIZE 137 } 138 return false; 139 } 140 141 template <bool INVARIANT> 142 bool IsConstantExprHelper<INVARIANT>::IsConstantExprShape( 143 const Shape &shape) const { 144 for (const auto &extent : shape) { 145 if (!(*this)(extent)) { 146 return false; 147 } 148 } 149 return true; 150 } 151 152 template <typename A> bool IsConstantExpr(const A &x) { 153 return IsConstantExprHelper<false>{}(x); 154 } 155 template bool IsConstantExpr(const Expr<SomeType> &); 156 template bool IsConstantExpr(const Expr<SomeInteger> &); 157 template bool IsConstantExpr(const Expr<SubscriptInteger> &); 158 template bool IsConstantExpr(const StructureConstructor &); 159 160 // IsScopeInvariantExpr() 161 template <typename A> bool IsScopeInvariantExpr(const A &x) { 162 return IsConstantExprHelper<true>{}(x); 163 } 164 template bool IsScopeInvariantExpr(const Expr<SomeType> &); 165 template bool IsScopeInvariantExpr(const Expr<SomeInteger> &); 166 template bool IsScopeInvariantExpr(const Expr<SubscriptInteger> &); 167 168 // IsActuallyConstant() 169 struct IsActuallyConstantHelper { 170 template <typename A> bool operator()(const A &) { return false; } 171 template <typename T> bool operator()(const Constant<T> &) { return true; } 172 template <typename T> bool operator()(const Parentheses<T> &x) { 173 return (*this)(x.left()); 174 } 175 template <typename T> bool operator()(const Expr<T> &x) { 176 return std::visit([=](const auto &y) { return (*this)(y); }, x.u); 177 } 178 bool operator()(const Expr<SomeType> &x) { 179 if (IsNullPointer(x)) { 180 return true; 181 } 182 return std::visit([this](const auto &y) { return (*this)(y); }, x.u); 183 } 184 template <typename A> bool operator()(const A *x) { return x && (*this)(*x); } 185 template <typename A> bool operator()(const std::optional<A> &x) { 186 return x && (*this)(*x); 187 } 188 }; 189 190 template <typename A> bool IsActuallyConstant(const A &x) { 191 return IsActuallyConstantHelper{}(x); 192 } 193 194 template bool IsActuallyConstant(const Expr<SomeType> &); 195 template bool IsActuallyConstant(const Expr<SomeInteger> &); 196 template bool IsActuallyConstant(const Expr<SubscriptInteger> &); 197 198 // Object pointer initialization checking predicate IsInitialDataTarget(). 199 // This code determines whether an expression is allowable as the static 200 // data address used to initialize a pointer with "=> x". See C765. 201 class IsInitialDataTargetHelper 202 : public AllTraverse<IsInitialDataTargetHelper, true> { 203 public: 204 using Base = AllTraverse<IsInitialDataTargetHelper, true>; 205 using Base::operator(); 206 explicit IsInitialDataTargetHelper(parser::ContextualMessages *m) 207 : Base{*this}, messages_{m} {} 208 209 bool emittedMessage() const { return emittedMessage_; } 210 211 bool operator()(const BOZLiteralConstant &) const { return false; } 212 bool operator()(const NullPointer &) const { return true; } 213 template <typename T> bool operator()(const Constant<T> &) const { 214 return false; 215 } 216 bool operator()(const semantics::Symbol &symbol) { 217 // This function checks only base symbols, not components. 218 const Symbol &ultimate{symbol.GetUltimate()}; 219 if (const auto *assoc{ 220 ultimate.detailsIf<semantics::AssocEntityDetails>()}) { 221 if (const auto &expr{assoc->expr()}) { 222 if (IsVariable(*expr)) { 223 return (*this)(*expr); 224 } else if (messages_) { 225 messages_->Say( 226 "An initial data target may not be an associated expression ('%s')"_err_en_US, 227 ultimate.name()); 228 emittedMessage_ = true; 229 } 230 } 231 return false; 232 } else if (!ultimate.attrs().test(semantics::Attr::TARGET)) { 233 if (messages_) { 234 messages_->Say( 235 "An initial data target may not be a reference to an object '%s' that lacks the TARGET attribute"_err_en_US, 236 ultimate.name()); 237 emittedMessage_ = true; 238 } 239 return false; 240 } else if (!IsSaved(ultimate)) { 241 if (messages_) { 242 messages_->Say( 243 "An initial data target may not be a reference to an object '%s' that lacks the SAVE attribute"_err_en_US, 244 ultimate.name()); 245 emittedMessage_ = true; 246 } 247 return false; 248 } else { 249 return CheckVarOrComponent(ultimate); 250 } 251 } 252 bool operator()(const StaticDataObject &) const { return false; } 253 bool operator()(const TypeParamInquiry &) const { return false; } 254 bool operator()(const Triplet &x) const { 255 return IsConstantExpr(x.lower()) && IsConstantExpr(x.upper()) && 256 IsConstantExpr(x.stride()); 257 } 258 bool operator()(const Subscript &x) const { 259 return std::visit(common::visitors{ 260 [&](const Triplet &t) { return (*this)(t); }, 261 [&](const auto &y) { 262 return y.value().Rank() == 0 && 263 IsConstantExpr(y.value()); 264 }, 265 }, 266 x.u); 267 } 268 bool operator()(const CoarrayRef &) const { return false; } 269 bool operator()(const Component &x) { 270 return CheckVarOrComponent(x.GetLastSymbol()) && (*this)(x.base()); 271 } 272 bool operator()(const Substring &x) const { 273 return IsConstantExpr(x.lower()) && IsConstantExpr(x.upper()) && 274 (*this)(x.parent()); 275 } 276 bool operator()(const DescriptorInquiry &) const { return false; } 277 template <typename T> bool operator()(const ArrayConstructor<T> &) const { 278 return false; 279 } 280 bool operator()(const StructureConstructor &) const { return false; } 281 template <typename D, typename R, typename... O> 282 bool operator()(const Operation<D, R, O...> &) const { 283 return false; 284 } 285 template <typename T> bool operator()(const Parentheses<T> &x) const { 286 return (*this)(x.left()); 287 } 288 bool operator()(const ProcedureRef &x) const { 289 if (const SpecificIntrinsic * intrinsic{x.proc().GetSpecificIntrinsic()}) { 290 return intrinsic->characteristics.value().attrs.test( 291 characteristics::Procedure::Attr::NullPointer); 292 } 293 return false; 294 } 295 bool operator()(const Relational<SomeType> &) const { return false; } 296 297 private: 298 bool CheckVarOrComponent(const semantics::Symbol &symbol) { 299 const Symbol &ultimate{symbol.GetUltimate()}; 300 if (IsAllocatable(ultimate)) { 301 if (messages_) { 302 messages_->Say( 303 "An initial data target may not be a reference to an ALLOCATABLE '%s'"_err_en_US, 304 ultimate.name()); 305 emittedMessage_ = true; 306 } 307 return false; 308 } else if (ultimate.Corank() > 0) { 309 if (messages_) { 310 messages_->Say( 311 "An initial data target may not be a reference to a coarray '%s'"_err_en_US, 312 ultimate.name()); 313 emittedMessage_ = true; 314 } 315 return false; 316 } 317 return true; 318 } 319 320 parser::ContextualMessages *messages_; 321 bool emittedMessage_{false}; 322 }; 323 324 bool IsInitialDataTarget( 325 const Expr<SomeType> &x, parser::ContextualMessages *messages) { 326 IsInitialDataTargetHelper helper{messages}; 327 bool result{helper(x)}; 328 if (!result && messages && !helper.emittedMessage()) { 329 messages->Say( 330 "An initial data target must be a designator with constant subscripts"_err_en_US); 331 } 332 return result; 333 } 334 335 bool IsInitialProcedureTarget(const semantics::Symbol &symbol) { 336 const auto &ultimate{symbol.GetUltimate()}; 337 return std::visit( 338 common::visitors{ 339 [](const semantics::SubprogramDetails &subp) { 340 return !subp.isDummy(); 341 }, 342 [](const semantics::SubprogramNameDetails &) { return true; }, 343 [&](const semantics::ProcEntityDetails &proc) { 344 return !semantics::IsPointer(ultimate) && !proc.isDummy(); 345 }, 346 [](const auto &) { return false; }, 347 }, 348 ultimate.details()); 349 } 350 351 bool IsInitialProcedureTarget(const ProcedureDesignator &proc) { 352 if (const auto *intrin{proc.GetSpecificIntrinsic()}) { 353 return !intrin->isRestrictedSpecific; 354 } else if (proc.GetComponent()) { 355 return false; 356 } else { 357 return IsInitialProcedureTarget(DEREF(proc.GetSymbol())); 358 } 359 } 360 361 bool IsInitialProcedureTarget(const Expr<SomeType> &expr) { 362 if (const auto *proc{std::get_if<ProcedureDesignator>(&expr.u)}) { 363 return IsInitialProcedureTarget(*proc); 364 } else { 365 return IsNullPointer(expr); 366 } 367 } 368 369 class ArrayConstantBoundChanger { 370 public: 371 ArrayConstantBoundChanger(ConstantSubscripts &&lbounds) 372 : lbounds_{std::move(lbounds)} {} 373 374 template <typename A> A ChangeLbounds(A &&x) const { 375 return std::move(x); // default case 376 } 377 template <typename T> Constant<T> ChangeLbounds(Constant<T> &&x) { 378 x.set_lbounds(std::move(lbounds_)); 379 return std::move(x); 380 } 381 template <typename T> Expr<T> ChangeLbounds(Parentheses<T> &&x) { 382 return ChangeLbounds( 383 std::move(x.left())); // Constant<> can be parenthesized 384 } 385 template <typename T> Expr<T> ChangeLbounds(Expr<T> &&x) { 386 return std::visit( 387 [&](auto &&x) { return Expr<T>{ChangeLbounds(std::move(x))}; }, 388 std::move(x.u)); // recurse until we hit a constant 389 } 390 391 private: 392 ConstantSubscripts &&lbounds_; 393 }; 394 395 // Converts, folds, and then checks type, rank, and shape of an 396 // initialization expression for a named constant, a non-pointer 397 // variable static initialization, a component default initializer, 398 // a type parameter default value, or instantiated type parameter value. 399 std::optional<Expr<SomeType>> NonPointerInitializationExpr(const Symbol &symbol, 400 Expr<SomeType> &&x, FoldingContext &context, 401 const semantics::Scope *instantiation) { 402 CHECK(!IsPointer(symbol)); 403 if (auto symTS{ 404 characteristics::TypeAndShape::Characterize(symbol, context)}) { 405 auto xType{x.GetType()}; 406 auto converted{ConvertToType(symTS->type(), Expr<SomeType>{x})}; 407 if (!converted && 408 symbol.owner().context().IsEnabled( 409 common::LanguageFeature::LogicalIntegerAssignment)) { 410 converted = DataConstantConversionExtension(context, symTS->type(), x); 411 if (converted && 412 symbol.owner().context().ShouldWarn( 413 common::LanguageFeature::LogicalIntegerAssignment)) { 414 context.messages().Say( 415 "nonstandard usage: initialization of %s with %s"_port_en_US, 416 symTS->type().AsFortran(), x.GetType().value().AsFortran()); 417 } 418 } 419 if (converted) { 420 auto folded{Fold(context, std::move(*converted))}; 421 if (IsActuallyConstant(folded)) { 422 int symRank{GetRank(symTS->shape())}; 423 if (IsImpliedShape(symbol)) { 424 if (folded.Rank() == symRank) { 425 return {std::move(folded)}; 426 } else { 427 context.messages().Say( 428 "Implied-shape parameter '%s' has rank %d but its initializer has rank %d"_err_en_US, 429 symbol.name(), symRank, folded.Rank()); 430 } 431 } else if (auto extents{AsConstantExtents(context, symTS->shape())}) { 432 if (folded.Rank() == 0 && symRank == 0) { 433 // symbol and constant are both scalars 434 return {std::move(folded)}; 435 } else if (folded.Rank() == 0 && symRank > 0) { 436 // expand the scalar constant to an array 437 return ScalarConstantExpander{std::move(*extents), 438 AsConstantExtents( 439 context, GetRawLowerBounds(context, NamedEntity{symbol}))} 440 .Expand(std::move(folded)); 441 } else if (auto resultShape{GetShape(context, folded)}) { 442 if (CheckConformance(context.messages(), symTS->shape(), 443 *resultShape, CheckConformanceFlags::None, 444 "initialized object", "initialization expression") 445 .value_or(false /*fail if not known now to conform*/)) { 446 // make a constant array with adjusted lower bounds 447 return ArrayConstantBoundChanger{ 448 std::move(*AsConstantExtents(context, 449 GetRawLowerBounds(context, NamedEntity{symbol})))} 450 .ChangeLbounds(std::move(folded)); 451 } 452 } 453 } else if (IsNamedConstant(symbol)) { 454 if (IsExplicitShape(symbol)) { 455 context.messages().Say( 456 "Named constant '%s' array must have constant shape"_err_en_US, 457 symbol.name()); 458 } else { 459 // Declaration checking handles other cases 460 } 461 } else { 462 context.messages().Say( 463 "Shape of initialized object '%s' must be constant"_err_en_US, 464 symbol.name()); 465 } 466 } else if (IsErrorExpr(folded)) { 467 } else if (IsLenTypeParameter(symbol)) { 468 return {std::move(folded)}; 469 } else if (IsKindTypeParameter(symbol)) { 470 if (instantiation) { 471 context.messages().Say( 472 "Value of kind type parameter '%s' (%s) must be a scalar INTEGER constant"_err_en_US, 473 symbol.name(), folded.AsFortran()); 474 } else { 475 return {std::move(folded)}; 476 } 477 } else if (IsNamedConstant(symbol)) { 478 context.messages().Say( 479 "Value of named constant '%s' (%s) cannot be computed as a constant value"_err_en_US, 480 symbol.name(), folded.AsFortran()); 481 } else { 482 context.messages().Say( 483 "Initialization expression for '%s' (%s) cannot be computed as a constant value"_err_en_US, 484 symbol.name(), folded.AsFortran()); 485 } 486 } else if (xType) { 487 context.messages().Say( 488 "Initialization expression cannot be converted to declared type of '%s' from %s"_err_en_US, 489 symbol.name(), xType->AsFortran()); 490 } else { 491 context.messages().Say( 492 "Initialization expression cannot be converted to declared type of '%s'"_err_en_US, 493 symbol.name()); 494 } 495 } 496 return std::nullopt; 497 } 498 499 // Specification expression validation (10.1.11(2), C1010) 500 class CheckSpecificationExprHelper 501 : public AnyTraverse<CheckSpecificationExprHelper, 502 std::optional<std::string>> { 503 public: 504 using Result = std::optional<std::string>; 505 using Base = AnyTraverse<CheckSpecificationExprHelper, Result>; 506 explicit CheckSpecificationExprHelper( 507 const semantics::Scope &s, FoldingContext &context) 508 : Base{*this}, scope_{s}, context_{context} {} 509 using Base::operator(); 510 511 Result operator()(const CoarrayRef &) const { return "coindexed reference"; } 512 513 Result operator()(const semantics::Symbol &symbol) const { 514 const auto &ultimate{symbol.GetUltimate()}; 515 if (const auto *assoc{ 516 ultimate.detailsIf<semantics::AssocEntityDetails>()}) { 517 return (*this)(assoc->expr()); 518 } else if (semantics::IsNamedConstant(ultimate) || 519 ultimate.owner().IsModule() || ultimate.owner().IsSubmodule()) { 520 return std::nullopt; 521 } else if (scope_.IsDerivedType() && 522 IsVariableName(ultimate)) { // C750, C754 523 return "derived type component or type parameter value not allowed to " 524 "reference variable '"s + 525 ultimate.name().ToString() + "'"; 526 } else if (IsDummy(ultimate)) { 527 if (ultimate.attrs().test(semantics::Attr::OPTIONAL)) { 528 return "reference to OPTIONAL dummy argument '"s + 529 ultimate.name().ToString() + "'"; 530 } else if (ultimate.attrs().test(semantics::Attr::INTENT_OUT)) { 531 return "reference to INTENT(OUT) dummy argument '"s + 532 ultimate.name().ToString() + "'"; 533 } else if (ultimate.has<semantics::ObjectEntityDetails>()) { 534 return std::nullopt; 535 } else { 536 return "dummy procedure argument"; 537 } 538 } else if (&symbol.owner() != &scope_ || &ultimate.owner() != &scope_) { 539 return std::nullopt; // host association is in play 540 } else if (const auto *object{ 541 ultimate.detailsIf<semantics::ObjectEntityDetails>()}) { 542 if (object->commonBlock()) { 543 return std::nullopt; 544 } 545 } 546 return "reference to local entity '"s + ultimate.name().ToString() + "'"; 547 } 548 549 Result operator()(const Component &x) const { 550 // Don't look at the component symbol. 551 return (*this)(x.base()); 552 } 553 Result operator()(const DescriptorInquiry &) const { 554 // Subtle: Uses of SIZE(), LBOUND(), &c. that are valid in specification 555 // expressions will have been converted to expressions over descriptor 556 // inquiries by Fold(). 557 return std::nullopt; 558 } 559 560 Result operator()(const TypeParamInquiry &inq) const { 561 if (scope_.IsDerivedType() && !IsConstantExpr(inq) && 562 inq.base() /* X%T, not local T */) { // C750, C754 563 return "non-constant reference to a type parameter inquiry not " 564 "allowed for derived type components or type parameter values"; 565 } 566 return std::nullopt; 567 } 568 569 Result operator()(const ProcedureRef &x) const { 570 if (const auto *symbol{x.proc().GetSymbol()}) { 571 const Symbol &ultimate{symbol->GetUltimate()}; 572 if (!semantics::IsPureProcedure(ultimate)) { 573 return "reference to impure function '"s + ultimate.name().ToString() + 574 "'"; 575 } 576 if (semantics::IsStmtFunction(ultimate)) { 577 return "reference to statement function '"s + 578 ultimate.name().ToString() + "'"; 579 } 580 if (scope_.IsDerivedType()) { // C750, C754 581 return "reference to function '"s + ultimate.name().ToString() + 582 "' not allowed for derived type components or type parameter" 583 " values"; 584 } 585 if (auto procChars{ 586 characteristics::Procedure::Characterize(x.proc(), context_)}) { 587 const auto iter{std::find_if(procChars->dummyArguments.begin(), 588 procChars->dummyArguments.end(), 589 [](const characteristics::DummyArgument &dummy) { 590 return std::holds_alternative<characteristics::DummyProcedure>( 591 dummy.u); 592 })}; 593 if (iter != procChars->dummyArguments.end()) { 594 return "reference to function '"s + ultimate.name().ToString() + 595 "' with dummy procedure argument '" + iter->name + '\''; 596 } 597 } 598 // References to internal functions are caught in expression semantics. 599 // TODO: other checks for standard module procedures 600 } else { 601 const SpecificIntrinsic &intrin{DEREF(x.proc().GetSpecificIntrinsic())}; 602 if (scope_.IsDerivedType()) { // C750, C754 603 if ((context_.intrinsics().IsIntrinsic(intrin.name) && 604 badIntrinsicsForComponents_.find(intrin.name) != 605 badIntrinsicsForComponents_.end()) || 606 IsProhibitedFunction(intrin.name)) { 607 return "reference to intrinsic '"s + intrin.name + 608 "' not allowed for derived type components or type parameter" 609 " values"; 610 } 611 if (context_.intrinsics().GetIntrinsicClass(intrin.name) == 612 IntrinsicClass::inquiryFunction && 613 !IsConstantExpr(x)) { 614 return "non-constant reference to inquiry intrinsic '"s + 615 intrin.name + 616 "' not allowed for derived type components or type" 617 " parameter values"; 618 } 619 } else if (intrin.name == "present") { 620 return std::nullopt; // no need to check argument(s) 621 } 622 if (IsConstantExpr(x)) { 623 // inquiry functions may not need to check argument(s) 624 return std::nullopt; 625 } 626 } 627 return (*this)(x.arguments()); 628 } 629 630 private: 631 const semantics::Scope &scope_; 632 FoldingContext &context_; 633 const std::set<std::string> badIntrinsicsForComponents_{ 634 "allocated", "associated", "extends_type_of", "present", "same_type_as"}; 635 static bool IsProhibitedFunction(std::string name) { return false; } 636 }; 637 638 template <typename A> 639 void CheckSpecificationExpr( 640 const A &x, const semantics::Scope &scope, FoldingContext &context) { 641 if (auto why{CheckSpecificationExprHelper{scope, context}(x)}) { 642 context.messages().Say( 643 "Invalid specification expression: %s"_err_en_US, *why); 644 } 645 } 646 647 template void CheckSpecificationExpr( 648 const Expr<SomeType> &, const semantics::Scope &, FoldingContext &); 649 template void CheckSpecificationExpr( 650 const Expr<SomeInteger> &, const semantics::Scope &, FoldingContext &); 651 template void CheckSpecificationExpr( 652 const Expr<SubscriptInteger> &, const semantics::Scope &, FoldingContext &); 653 template void CheckSpecificationExpr(const std::optional<Expr<SomeType>> &, 654 const semantics::Scope &, FoldingContext &); 655 template void CheckSpecificationExpr(const std::optional<Expr<SomeInteger>> &, 656 const semantics::Scope &, FoldingContext &); 657 template void CheckSpecificationExpr( 658 const std::optional<Expr<SubscriptInteger>> &, const semantics::Scope &, 659 FoldingContext &); 660 661 // IsSimplyContiguous() -- 9.5.4 662 class IsSimplyContiguousHelper 663 : public AnyTraverse<IsSimplyContiguousHelper, std::optional<bool>> { 664 public: 665 using Result = std::optional<bool>; // tri-state 666 using Base = AnyTraverse<IsSimplyContiguousHelper, Result>; 667 explicit IsSimplyContiguousHelper(FoldingContext &c) 668 : Base{*this}, context_{c} {} 669 using Base::operator(); 670 671 Result operator()(const semantics::Symbol &symbol) const { 672 const auto &ultimate{symbol.GetUltimate()}; 673 if (ultimate.attrs().test(semantics::Attr::CONTIGUOUS)) { 674 return true; 675 } else if (ultimate.Rank() == 0) { 676 // Extension: accept scalars as a degenerate case of 677 // simple contiguity to allow their use in contexts like 678 // data targets in pointer assignments with remapping. 679 return true; 680 } else if (semantics::IsPointer(ultimate) || 681 semantics::IsAssumedShape(ultimate)) { 682 return false; 683 } else if (const auto *details{ 684 ultimate.detailsIf<semantics::ObjectEntityDetails>()}) { 685 return !details->IsAssumedRank(); 686 } else if (auto assoc{Base::operator()(ultimate)}) { 687 return assoc; 688 } else { 689 return false; 690 } 691 } 692 693 Result operator()(const ArrayRef &x) const { 694 const auto &symbol{x.GetLastSymbol()}; 695 if (!(*this)(symbol).has_value()) { 696 return false; 697 } else if (auto rank{CheckSubscripts(x.subscript())}) { 698 if (x.Rank() == 0) { 699 return true; 700 } else if (*rank > 0) { 701 // a(1)%b(:,:) is contiguous if an only if a(1)%b is contiguous. 702 return (*this)(x.base()); 703 } else { 704 // a(:)%b(1,1) is not contiguous. 705 return false; 706 } 707 } else { 708 return false; 709 } 710 } 711 Result operator()(const CoarrayRef &x) const { 712 return CheckSubscripts(x.subscript()).has_value(); 713 } 714 Result operator()(const Component &x) const { 715 return x.base().Rank() == 0 && (*this)(x.GetLastSymbol()).value_or(false); 716 } 717 Result operator()(const ComplexPart &) const { return false; } 718 Result operator()(const Substring &) const { return false; } 719 720 Result operator()(const ProcedureRef &x) const { 721 if (auto chars{ 722 characteristics::Procedure::Characterize(x.proc(), context_)}) { 723 if (chars->functionResult) { 724 const auto &result{*chars->functionResult}; 725 return !result.IsProcedurePointer() && 726 result.attrs.test(characteristics::FunctionResult::Attr::Pointer) && 727 result.attrs.test( 728 characteristics::FunctionResult::Attr::Contiguous); 729 } 730 } 731 return false; 732 } 733 734 private: 735 // If the subscripts can possibly be on a simply-contiguous array reference, 736 // return the rank. 737 static std::optional<int> CheckSubscripts( 738 const std::vector<Subscript> &subscript) { 739 bool anyTriplet{false}; 740 int rank{0}; 741 for (auto j{subscript.size()}; j-- > 0;) { 742 if (const auto *triplet{std::get_if<Triplet>(&subscript[j].u)}) { 743 if (!triplet->IsStrideOne()) { 744 return std::nullopt; 745 } else if (anyTriplet) { 746 if (triplet->lower() || triplet->upper()) { 747 // all triplets before the last one must be just ":" 748 return std::nullopt; 749 } 750 } else { 751 anyTriplet = true; 752 } 753 ++rank; 754 } else if (anyTriplet || subscript[j].Rank() > 0) { 755 return std::nullopt; 756 } 757 } 758 return rank; 759 } 760 761 FoldingContext &context_; 762 }; 763 764 template <typename A> 765 bool IsSimplyContiguous(const A &x, FoldingContext &context) { 766 if (IsVariable(x)) { 767 auto known{IsSimplyContiguousHelper{context}(x)}; 768 return known && *known; 769 } else { 770 return true; // not a variable 771 } 772 } 773 774 template bool IsSimplyContiguous(const Expr<SomeType> &, FoldingContext &); 775 776 // IsErrorExpr() 777 struct IsErrorExprHelper : public AnyTraverse<IsErrorExprHelper, bool> { 778 using Result = bool; 779 using Base = AnyTraverse<IsErrorExprHelper, Result>; 780 IsErrorExprHelper() : Base{*this} {} 781 using Base::operator(); 782 783 bool operator()(const SpecificIntrinsic &x) { 784 return x.name == IntrinsicProcTable::InvalidName; 785 } 786 }; 787 788 template <typename A> bool IsErrorExpr(const A &x) { 789 return IsErrorExprHelper{}(x); 790 } 791 792 template bool IsErrorExpr(const Expr<SomeType> &); 793 794 } // namespace Fortran::evaluate 795