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