1 //===-- lib/Semantics/tools.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/Parser/tools.h"
10 #include "flang/Common/Fortran.h"
11 #include "flang/Common/indirection.h"
12 #include "flang/Parser/dump-parse-tree.h"
13 #include "flang/Parser/message.h"
14 #include "flang/Parser/parse-tree.h"
15 #include "flang/Semantics/scope.h"
16 #include "flang/Semantics/semantics.h"
17 #include "flang/Semantics/symbol.h"
18 #include "flang/Semantics/tools.h"
19 #include "flang/Semantics/type.h"
20 #include "llvm/Support/raw_ostream.h"
21 #include <algorithm>
22 #include <set>
23 #include <variant>
24 
25 namespace Fortran::semantics {
26 
27 // Find this or containing scope that matches predicate
28 static const Scope *FindScopeContaining(
29     const Scope &start, std::function<bool(const Scope &)> predicate) {
30   for (const Scope *scope{&start};; scope = &scope->parent()) {
31     if (predicate(*scope)) {
32       return scope;
33     }
34     if (scope->IsGlobal()) {
35       return nullptr;
36     }
37   }
38 }
39 
40 const Scope *FindModuleContaining(const Scope &start) {
41   return FindScopeContaining(
42       start, [](const Scope &scope) { return scope.IsModule(); });
43 }
44 
45 const Scope *FindProgramUnitContaining(const Scope &start) {
46   return FindScopeContaining(start, [](const Scope &scope) {
47     switch (scope.kind()) {
48     case Scope::Kind::Module:
49     case Scope::Kind::MainProgram:
50     case Scope::Kind::Subprogram:
51     case Scope::Kind::BlockData:
52       return true;
53     default:
54       return false;
55     }
56   });
57 }
58 
59 const Scope *FindProgramUnitContaining(const Symbol &symbol) {
60   return FindProgramUnitContaining(symbol.owner());
61 }
62 
63 const Scope *FindPureProcedureContaining(const Scope &start) {
64   // N.B. We only need to examine the innermost containing program unit
65   // because an internal subprogram of a pure subprogram must also
66   // be pure (C1592).
67   if (const Scope * scope{FindProgramUnitContaining(start)}) {
68     if (IsPureProcedure(*scope)) {
69       return scope;
70     }
71   }
72   return nullptr;
73 }
74 
75 Tristate IsDefinedAssignment(
76     const std::optional<evaluate::DynamicType> &lhsType, int lhsRank,
77     const std::optional<evaluate::DynamicType> &rhsType, int rhsRank) {
78   if (!lhsType || !rhsType) {
79     return Tristate::No; // error or rhs is untyped
80   }
81   TypeCategory lhsCat{lhsType->category()};
82   TypeCategory rhsCat{rhsType->category()};
83   if (rhsRank > 0 && lhsRank != rhsRank) {
84     return Tristate::Yes;
85   } else if (lhsCat != TypeCategory::Derived) {
86     return ToTristate(lhsCat != rhsCat &&
87         (!IsNumericTypeCategory(lhsCat) || !IsNumericTypeCategory(rhsCat)));
88   } else {
89     const auto *lhsDerived{evaluate::GetDerivedTypeSpec(lhsType)};
90     const auto *rhsDerived{evaluate::GetDerivedTypeSpec(rhsType)};
91     if (lhsDerived && rhsDerived && *lhsDerived == *rhsDerived) {
92       return Tristate::Maybe; // TYPE(t) = TYPE(t) can be defined or
93                               // intrinsic
94     } else {
95       return Tristate::Yes;
96     }
97   }
98 }
99 
100 bool IsIntrinsicRelational(common::RelationalOperator opr,
101     const evaluate::DynamicType &type0, int rank0,
102     const evaluate::DynamicType &type1, int rank1) {
103   if (!evaluate::AreConformable(rank0, rank1)) {
104     return false;
105   } else {
106     auto cat0{type0.category()};
107     auto cat1{type1.category()};
108     if (IsNumericTypeCategory(cat0) && IsNumericTypeCategory(cat1)) {
109       // numeric types: EQ/NE always ok, others ok for non-complex
110       return opr == common::RelationalOperator::EQ ||
111           opr == common::RelationalOperator::NE ||
112           (cat0 != TypeCategory::Complex && cat1 != TypeCategory::Complex);
113     } else {
114       // not both numeric: only Character is ok
115       return cat0 == TypeCategory::Character && cat1 == TypeCategory::Character;
116     }
117   }
118 }
119 
120 bool IsIntrinsicNumeric(const evaluate::DynamicType &type0) {
121   return IsNumericTypeCategory(type0.category());
122 }
123 bool IsIntrinsicNumeric(const evaluate::DynamicType &type0, int rank0,
124     const evaluate::DynamicType &type1, int rank1) {
125   return evaluate::AreConformable(rank0, rank1) &&
126       IsNumericTypeCategory(type0.category()) &&
127       IsNumericTypeCategory(type1.category());
128 }
129 
130 bool IsIntrinsicLogical(const evaluate::DynamicType &type0) {
131   return type0.category() == TypeCategory::Logical;
132 }
133 bool IsIntrinsicLogical(const evaluate::DynamicType &type0, int rank0,
134     const evaluate::DynamicType &type1, int rank1) {
135   return evaluate::AreConformable(rank0, rank1) &&
136       type0.category() == TypeCategory::Logical &&
137       type1.category() == TypeCategory::Logical;
138 }
139 
140 bool IsIntrinsicConcat(const evaluate::DynamicType &type0, int rank0,
141     const evaluate::DynamicType &type1, int rank1) {
142   return evaluate::AreConformable(rank0, rank1) &&
143       type0.category() == TypeCategory::Character &&
144       type1.category() == TypeCategory::Character &&
145       type0.kind() == type1.kind();
146 }
147 
148 bool IsGenericDefinedOp(const Symbol &symbol) {
149   const Symbol &ultimate{symbol.GetUltimate()};
150   if (const auto *generic{ultimate.detailsIf<GenericDetails>()}) {
151     return generic->kind().IsDefinedOperator();
152   } else if (const auto *misc{ultimate.detailsIf<MiscDetails>()}) {
153     return misc->kind() == MiscDetails::Kind::TypeBoundDefinedOp;
154   } else {
155     return false;
156   }
157 }
158 
159 bool IsCommonBlockContaining(const Symbol &block, const Symbol &object) {
160   const auto &objects{block.get<CommonBlockDetails>().objects()};
161   auto found{std::find(objects.begin(), objects.end(), object)};
162   return found != objects.end();
163 }
164 
165 bool IsUseAssociated(const Symbol &symbol, const Scope &scope) {
166   const Scope *owner{FindProgramUnitContaining(symbol.GetUltimate().owner())};
167   return owner && owner->kind() == Scope::Kind::Module &&
168       owner != FindProgramUnitContaining(scope);
169 }
170 
171 bool DoesScopeContain(
172     const Scope *maybeAncestor, const Scope &maybeDescendent) {
173   return maybeAncestor && !maybeDescendent.IsGlobal() &&
174       FindScopeContaining(maybeDescendent.parent(),
175           [&](const Scope &scope) { return &scope == maybeAncestor; });
176 }
177 
178 bool DoesScopeContain(const Scope *maybeAncestor, const Symbol &symbol) {
179   return DoesScopeContain(maybeAncestor, symbol.owner());
180 }
181 
182 bool IsHostAssociated(const Symbol &symbol, const Scope &scope) {
183   const Scope *subprogram{FindProgramUnitContaining(scope)};
184   return subprogram &&
185       DoesScopeContain(FindProgramUnitContaining(symbol), *subprogram);
186 }
187 
188 bool IsInStmtFunction(const Symbol &symbol) {
189   if (const Symbol * function{symbol.owner().symbol()}) {
190     return IsStmtFunction(*function);
191   }
192   return false;
193 }
194 
195 bool IsStmtFunctionDummy(const Symbol &symbol) {
196   return IsDummy(symbol) && IsInStmtFunction(symbol);
197 }
198 
199 bool IsStmtFunctionResult(const Symbol &symbol) {
200   return IsFunctionResult(symbol) && IsInStmtFunction(symbol);
201 }
202 
203 bool IsPointerDummy(const Symbol &symbol) {
204   return IsPointer(symbol) && IsDummy(symbol);
205 }
206 
207 // proc-name
208 bool IsProcName(const Symbol &symbol) {
209   return symbol.GetUltimate().has<ProcEntityDetails>();
210 }
211 
212 bool IsBindCProcedure(const Symbol &symbol) {
213   if (const auto *procDetails{symbol.detailsIf<ProcEntityDetails>()}) {
214     if (const Symbol * procInterface{procDetails->interface().symbol()}) {
215       // procedure component with a BIND(C) interface
216       return IsBindCProcedure(*procInterface);
217     }
218   }
219   return symbol.attrs().test(Attr::BIND_C) && IsProcedure(symbol);
220 }
221 
222 bool IsBindCProcedure(const Scope &scope) {
223   if (const Symbol * symbol{scope.GetSymbol()}) {
224     return IsBindCProcedure(*symbol);
225   } else {
226     return false;
227   }
228 }
229 
230 static const Symbol *FindPointerComponent(
231     const Scope &scope, std::set<const Scope *> &visited) {
232   if (!scope.IsDerivedType()) {
233     return nullptr;
234   }
235   if (!visited.insert(&scope).second) {
236     return nullptr;
237   }
238   // If there's a top-level pointer component, return it for clearer error
239   // messaging.
240   for (const auto &pair : scope) {
241     const Symbol &symbol{*pair.second};
242     if (IsPointer(symbol)) {
243       return &symbol;
244     }
245   }
246   for (const auto &pair : scope) {
247     const Symbol &symbol{*pair.second};
248     if (const auto *details{symbol.detailsIf<ObjectEntityDetails>()}) {
249       if (const DeclTypeSpec * type{details->type()}) {
250         if (const DerivedTypeSpec * derived{type->AsDerived()}) {
251           if (const Scope * nested{derived->scope()}) {
252             if (const Symbol *
253                 pointer{FindPointerComponent(*nested, visited)}) {
254               return pointer;
255             }
256           }
257         }
258       }
259     }
260   }
261   return nullptr;
262 }
263 
264 const Symbol *FindPointerComponent(const Scope &scope) {
265   std::set<const Scope *> visited;
266   return FindPointerComponent(scope, visited);
267 }
268 
269 const Symbol *FindPointerComponent(const DerivedTypeSpec &derived) {
270   if (const Scope * scope{derived.scope()}) {
271     return FindPointerComponent(*scope);
272   } else {
273     return nullptr;
274   }
275 }
276 
277 const Symbol *FindPointerComponent(const DeclTypeSpec &type) {
278   if (const DerivedTypeSpec * derived{type.AsDerived()}) {
279     return FindPointerComponent(*derived);
280   } else {
281     return nullptr;
282   }
283 }
284 
285 const Symbol *FindPointerComponent(const DeclTypeSpec *type) {
286   return type ? FindPointerComponent(*type) : nullptr;
287 }
288 
289 const Symbol *FindPointerComponent(const Symbol &symbol) {
290   return IsPointer(symbol) ? &symbol : FindPointerComponent(symbol.GetType());
291 }
292 
293 // C1594 specifies several ways by which an object might be globally visible.
294 const Symbol *FindExternallyVisibleObject(
295     const Symbol &object, const Scope &scope) {
296   // TODO: Storage association with any object for which this predicate holds,
297   // once EQUIVALENCE is supported.
298   if (IsUseAssociated(object, scope) || IsHostAssociated(object, scope) ||
299       (IsPureProcedure(scope) && IsPointerDummy(object)) ||
300       (IsIntentIn(object) && IsDummy(object))) {
301     return &object;
302   } else if (const Symbol * block{FindCommonBlockContaining(object)}) {
303     return block;
304   } else {
305     return nullptr;
306   }
307 }
308 
309 bool ExprHasTypeCategory(
310     const SomeExpr &expr, const common::TypeCategory &type) {
311   auto dynamicType{expr.GetType()};
312   return dynamicType && dynamicType->category() == type;
313 }
314 
315 bool ExprTypeKindIsDefault(
316     const SomeExpr &expr, const SemanticsContext &context) {
317   auto dynamicType{expr.GetType()};
318   return dynamicType &&
319       dynamicType->category() != common::TypeCategory::Derived &&
320       dynamicType->kind() == context.GetDefaultKind(dynamicType->category());
321 }
322 
323 // If an analyzed expr or assignment is missing, dump the node and die.
324 template <typename T>
325 static void CheckMissingAnalysis(bool absent, const T &x) {
326   if (absent) {
327     std::string buf;
328     llvm::raw_string_ostream ss{buf};
329     ss << "node has not been analyzed:\n";
330     parser::DumpTree(ss, x);
331     common::die(ss.str().c_str());
332   }
333 }
334 
335 const SomeExpr *GetExprHelper::Get(const parser::Expr &x) {
336   CheckMissingAnalysis(!x.typedExpr, x);
337   return common::GetPtrFromOptional(x.typedExpr->v);
338 }
339 const SomeExpr *GetExprHelper::Get(const parser::Variable &x) {
340   CheckMissingAnalysis(!x.typedExpr, x);
341   return common::GetPtrFromOptional(x.typedExpr->v);
342 }
343 
344 const evaluate::Assignment *GetAssignment(const parser::AssignmentStmt &x) {
345   CheckMissingAnalysis(!x.typedAssignment, x);
346   return common::GetPtrFromOptional(x.typedAssignment->v);
347 }
348 const evaluate::Assignment *GetAssignment(
349     const parser::PointerAssignmentStmt &x) {
350   CheckMissingAnalysis(!x.typedAssignment, x);
351   return common::GetPtrFromOptional(x.typedAssignment->v);
352 }
353 
354 const Symbol *FindInterface(const Symbol &symbol) {
355   return std::visit(
356       common::visitors{
357           [](const ProcEntityDetails &details) {
358             return details.interface().symbol();
359           },
360           [](const ProcBindingDetails &details) { return &details.symbol(); },
361           [](const auto &) -> const Symbol * { return nullptr; },
362       },
363       symbol.details());
364 }
365 
366 const Symbol *FindSubprogram(const Symbol &symbol) {
367   return std::visit(
368       common::visitors{
369           [&](const ProcEntityDetails &details) -> const Symbol * {
370             if (const Symbol * interface{details.interface().symbol()}) {
371               return FindSubprogram(*interface);
372             } else {
373               return &symbol;
374             }
375           },
376           [](const ProcBindingDetails &details) {
377             return FindSubprogram(details.symbol());
378           },
379           [&](const SubprogramDetails &) { return &symbol; },
380           [](const UseDetails &details) {
381             return FindSubprogram(details.symbol());
382           },
383           [](const HostAssocDetails &details) {
384             return FindSubprogram(details.symbol());
385           },
386           [](const auto &) -> const Symbol * { return nullptr; },
387       },
388       symbol.details());
389 }
390 
391 const Symbol *FindFunctionResult(const Symbol &symbol) {
392   if (const Symbol * subp{FindSubprogram(symbol)}) {
393     if (const auto &subpDetails{subp->detailsIf<SubprogramDetails>()}) {
394       if (subpDetails->isFunction()) {
395         return &subpDetails->result();
396       }
397     }
398   }
399   return nullptr;
400 }
401 
402 const Symbol *FindOverriddenBinding(const Symbol &symbol) {
403   if (symbol.has<ProcBindingDetails>()) {
404     if (const DeclTypeSpec * parentType{FindParentTypeSpec(symbol.owner())}) {
405       if (const DerivedTypeSpec * parentDerived{parentType->AsDerived()}) {
406         if (const Scope * parentScope{parentDerived->typeSymbol().scope()}) {
407           return parentScope->FindComponent(symbol.name());
408         }
409       }
410     }
411   }
412   return nullptr;
413 }
414 
415 const DeclTypeSpec *FindParentTypeSpec(const DerivedTypeSpec &derived) {
416   return FindParentTypeSpec(derived.typeSymbol());
417 }
418 
419 const DeclTypeSpec *FindParentTypeSpec(const DeclTypeSpec &decl) {
420   if (const DerivedTypeSpec * derived{decl.AsDerived()}) {
421     return FindParentTypeSpec(*derived);
422   } else {
423     return nullptr;
424   }
425 }
426 
427 const DeclTypeSpec *FindParentTypeSpec(const Scope &scope) {
428   if (scope.kind() == Scope::Kind::DerivedType) {
429     if (const auto *symbol{scope.symbol()}) {
430       return FindParentTypeSpec(*symbol);
431     }
432   }
433   return nullptr;
434 }
435 
436 const DeclTypeSpec *FindParentTypeSpec(const Symbol &symbol) {
437   if (const Scope * scope{symbol.scope()}) {
438     if (const auto *details{symbol.detailsIf<DerivedTypeDetails>()}) {
439       if (const Symbol * parent{details->GetParentComponent(*scope)}) {
440         return parent->GetType();
441       }
442     }
443   }
444   return nullptr;
445 }
446 
447 bool IsExtensibleType(const DerivedTypeSpec *derived) {
448   return derived && !IsIsoCType(derived) &&
449       !derived->typeSymbol().attrs().test(Attr::BIND_C) &&
450       !derived->typeSymbol().get<DerivedTypeDetails>().sequence();
451 }
452 
453 bool IsBuiltinDerivedType(const DerivedTypeSpec *derived, const char *name) {
454   if (!derived) {
455     return false;
456   } else {
457     const auto &symbol{derived->typeSymbol()};
458     return symbol.owner().IsModule() &&
459         symbol.owner().GetName().value() == "__fortran_builtins" &&
460         symbol.name() == "__builtin_"s + name;
461   }
462 }
463 
464 bool IsIsoCType(const DerivedTypeSpec *derived) {
465   return IsBuiltinDerivedType(derived, "c_ptr") ||
466       IsBuiltinDerivedType(derived, "c_funptr");
467 }
468 
469 bool IsTeamType(const DerivedTypeSpec *derived) {
470   return IsBuiltinDerivedType(derived, "team_type");
471 }
472 
473 bool IsEventTypeOrLockType(const DerivedTypeSpec *derivedTypeSpec) {
474   return IsBuiltinDerivedType(derivedTypeSpec, "event_type") ||
475       IsBuiltinDerivedType(derivedTypeSpec, "lock_type");
476 }
477 
478 bool IsOrContainsEventOrLockComponent(const Symbol &symbol) {
479   if (const Symbol * root{GetAssociationRoot(symbol)}) {
480     if (const auto *details{root->detailsIf<ObjectEntityDetails>()}) {
481       if (const DeclTypeSpec * type{details->type()}) {
482         if (const DerivedTypeSpec * derived{type->AsDerived()}) {
483           return IsEventTypeOrLockType(derived) ||
484               FindEventOrLockPotentialComponent(*derived);
485         }
486       }
487     }
488   }
489   return false;
490 }
491 
492 // Check this symbol suitable as a type-bound procedure - C769
493 bool CanBeTypeBoundProc(const Symbol *symbol) {
494   if (!symbol || IsDummy(*symbol) || IsProcedurePointer(*symbol)) {
495     return false;
496   } else if (symbol->has<SubprogramNameDetails>()) {
497     return symbol->owner().kind() == Scope::Kind::Module;
498   } else if (auto *details{symbol->detailsIf<SubprogramDetails>()}) {
499     return symbol->owner().kind() == Scope::Kind::Module ||
500         details->isInterface();
501   } else if (const auto *proc{symbol->detailsIf<ProcEntityDetails>()}) {
502     return !symbol->attrs().test(Attr::INTRINSIC) &&
503         proc->HasExplicitInterface();
504   } else {
505     return false;
506   }
507 }
508 
509 bool IsInitialized(const Symbol &symbol) {
510   if (symbol.test(Symbol::Flag::InDataStmt)) {
511     return true;
512   } else if (IsNamedConstant(symbol)) {
513     return false;
514   } else if (const auto *object{symbol.detailsIf<ObjectEntityDetails>()}) {
515     if (IsAllocatable(symbol) || object->init()) {
516       return true;
517     }
518     if (!IsPointer(symbol) && object->type()) {
519       if (const auto *derived{object->type()->AsDerived()}) {
520         if (derived->HasDefaultInitialization()) {
521           return true;
522         }
523       }
524     }
525   } else if (const auto *proc{symbol.detailsIf<ProcEntityDetails>()}) {
526     return proc->init().has_value();
527   }
528   return false;
529 }
530 
531 bool HasIntrinsicTypeName(const Symbol &symbol) {
532   std::string name{symbol.name().ToString()};
533   if (name == "doubleprecision") {
534     return true;
535   } else if (name == "derived") {
536     return false;
537   } else {
538     for (int i{0}; i != common::TypeCategory_enumSize; ++i) {
539       if (name == parser::ToLowerCaseLetters(EnumToString(TypeCategory{i}))) {
540         return true;
541       }
542     }
543     return false;
544   }
545 }
546 
547 bool IsSeparateModuleProcedureInterface(const Symbol *symbol) {
548   if (symbol && symbol->attrs().test(Attr::MODULE)) {
549     if (auto *details{symbol->detailsIf<SubprogramDetails>()}) {
550       return details->isInterface();
551     }
552   }
553   return false;
554 }
555 
556 bool IsFinalizable(const Symbol &symbol) {
557   if (const DeclTypeSpec * type{symbol.GetType()}) {
558     if (const DerivedTypeSpec * derived{type->AsDerived()}) {
559       return IsFinalizable(*derived);
560     }
561   }
562   return false;
563 }
564 
565 bool IsFinalizable(const DerivedTypeSpec &derived) {
566   ScopeComponentIterator components{derived};
567   return std::find_if(components.begin(), components.end(),
568              [](const Symbol &x) { return x.has<FinalProcDetails>(); }) !=
569       components.end();
570 }
571 
572 // TODO The following function returns true for all types with FINAL procedures
573 // This is because we don't yet fill in the data for FinalProcDetails
574 bool HasImpureFinal(const DerivedTypeSpec &derived) {
575   ScopeComponentIterator components{derived};
576   return std::find_if(
577              components.begin(), components.end(), [](const Symbol &x) {
578                return x.has<FinalProcDetails>() && !x.attrs().test(Attr::PURE);
579              }) != components.end();
580 }
581 
582 bool IsCoarray(const Symbol &symbol) { return symbol.Corank() > 0; }
583 
584 bool IsAutomaticObject(const Symbol &symbol) {
585   if (IsDummy(symbol) || IsPointer(symbol) || IsAllocatable(symbol)) {
586     return false;
587   }
588   if (const DeclTypeSpec * type{symbol.GetType()}) {
589     if (type->category() == DeclTypeSpec::Character) {
590       ParamValue length{type->characterTypeSpec().length()};
591       if (length.isExplicit()) {
592         if (MaybeIntExpr lengthExpr{length.GetExplicit()}) {
593           if (!ToInt64(lengthExpr)) {
594             return true;
595           }
596         }
597       }
598     }
599   }
600   if (symbol.IsObjectArray()) {
601     for (const ShapeSpec &spec : symbol.get<ObjectEntityDetails>().shape()) {
602       auto &lbound{spec.lbound().GetExplicit()};
603       auto &ubound{spec.ubound().GetExplicit()};
604       if ((lbound && !evaluate::ToInt64(*lbound)) ||
605           (ubound && !evaluate::ToInt64(*ubound))) {
606         return true;
607       }
608     }
609   }
610   return false;
611 }
612 
613 bool IsAssumedLengthCharacter(const Symbol &symbol) {
614   if (const DeclTypeSpec * type{symbol.GetType()}) {
615     return type->category() == DeclTypeSpec::Character &&
616         type->characterTypeSpec().length().isAssumed();
617   } else {
618     return false;
619   }
620 }
621 
622 bool IsInBlankCommon(const Symbol &symbol) {
623   if (FindCommonBlockContaining(symbol)) {
624     if (const auto *details{
625             symbol.detailsIf<semantics::ObjectEntityDetails>()}) {
626       if (details->commonBlock()) {
627         if (details->commonBlock()->name().empty()) {
628           return true;
629         }
630       }
631     }
632   }
633   return false;
634 }
635 
636 // C722 and C723:  For a function to be assumed length, it must be external and
637 // of CHARACTER type
638 bool IsExternal(const Symbol &symbol) {
639   return (symbol.has<SubprogramDetails>() && symbol.owner().IsGlobal()) ||
640       symbol.attrs().test(Attr::EXTERNAL);
641 }
642 
643 const Symbol *IsExternalInPureContext(
644     const Symbol &symbol, const Scope &scope) {
645   if (const auto *pureProc{FindPureProcedureContaining(scope)}) {
646     if (const Symbol * root{GetAssociationRoot(symbol)}) {
647       if (const Symbol *
648           visible{FindExternallyVisibleObject(*root, *pureProc)}) {
649         return visible;
650       }
651     }
652   }
653   return nullptr;
654 }
655 
656 PotentialComponentIterator::const_iterator FindPolymorphicPotentialComponent(
657     const DerivedTypeSpec &derived) {
658   PotentialComponentIterator potentials{derived};
659   return std::find_if(
660       potentials.begin(), potentials.end(), [](const Symbol &component) {
661         if (const auto *details{component.detailsIf<ObjectEntityDetails>()}) {
662           const DeclTypeSpec *type{details->type()};
663           return type && type->IsPolymorphic();
664         }
665         return false;
666       });
667 }
668 
669 bool IsOrContainsPolymorphicComponent(const Symbol &symbol) {
670   if (const Symbol * root{GetAssociationRoot(symbol)}) {
671     if (const auto *details{root->detailsIf<ObjectEntityDetails>()}) {
672       if (const DeclTypeSpec * type{details->type()}) {
673         if (type->IsPolymorphic()) {
674           return true;
675         }
676         if (const DerivedTypeSpec * derived{type->AsDerived()}) {
677           return (bool)FindPolymorphicPotentialComponent(*derived);
678         }
679       }
680     }
681   }
682   return false;
683 }
684 
685 bool InProtectedContext(const Symbol &symbol, const Scope &currentScope) {
686   return IsProtected(symbol) && !IsHostAssociated(symbol, currentScope);
687 }
688 
689 // C1101 and C1158
690 // TODO Need to check for a coindexed object (why? C1103?)
691 std::optional<parser::MessageFixedText> WhyNotModifiable(
692     const Symbol &symbol, const Scope &scope) {
693   const Symbol *root{GetAssociationRoot(symbol)};
694   if (!root) {
695     return "'%s' is construct associated with an expression"_en_US;
696   } else if (InProtectedContext(*root, scope)) {
697     return "'%s' is protected in this scope"_en_US;
698   } else if (IsExternalInPureContext(*root, scope)) {
699     return "'%s' is externally visible and referenced in a pure"
700            " procedure"_en_US;
701   } else if (IsOrContainsEventOrLockComponent(*root)) {
702     return "'%s' is an entity with either an EVENT_TYPE or LOCK_TYPE"_en_US;
703   } else if (IsIntentIn(*root)) {
704     return "'%s' is an INTENT(IN) dummy argument"_en_US;
705   } else if (!IsVariableName(*root)) {
706     return "'%s' is not a variable"_en_US;
707   } else {
708     return std::nullopt;
709   }
710 }
711 
712 std::optional<parser::Message> WhyNotModifiable(parser::CharBlock at,
713     const SomeExpr &expr, const Scope &scope, bool vectorSubscriptIsOk) {
714   if (!evaluate::IsVariable(expr)) {
715     return parser::Message{at, "Expression is not a variable"_en_US};
716   } else if (auto dataRef{evaluate::ExtractDataRef(expr, true)}) {
717     if (!vectorSubscriptIsOk && evaluate::HasVectorSubscript(expr)) {
718       return parser::Message{at, "Variable has a vector subscript"_en_US};
719     }
720     const Symbol &symbol{dataRef->GetFirstSymbol()};
721     if (auto maybeWhy{WhyNotModifiable(symbol, scope)}) {
722       return parser::Message{symbol.name(),
723           parser::MessageFormattedText{std::move(*maybeWhy), symbol.name()}};
724     }
725   } else {
726     // reference to function returning POINTER
727   }
728   return std::nullopt;
729 }
730 
731 class ImageControlStmtHelper {
732   using ImageControlStmts = std::variant<parser::ChangeTeamConstruct,
733       parser::CriticalConstruct, parser::EventPostStmt, parser::EventWaitStmt,
734       parser::FormTeamStmt, parser::LockStmt, parser::StopStmt,
735       parser::SyncAllStmt, parser::SyncImagesStmt, parser::SyncMemoryStmt,
736       parser::SyncTeamStmt, parser::UnlockStmt>;
737 
738 public:
739   template <typename T> bool operator()(const T &) {
740     return common::HasMember<T, ImageControlStmts>;
741   }
742   template <typename T> bool operator()(const common::Indirection<T> &x) {
743     return (*this)(x.value());
744   }
745   bool operator()(const parser::AllocateStmt &stmt) {
746     const auto &allocationList{std::get<std::list<parser::Allocation>>(stmt.t)};
747     for (const auto &allocation : allocationList) {
748       const auto &allocateObject{
749           std::get<parser::AllocateObject>(allocation.t)};
750       if (IsCoarrayObject(allocateObject)) {
751         return true;
752       }
753     }
754     return false;
755   }
756   bool operator()(const parser::DeallocateStmt &stmt) {
757     const auto &allocateObjectList{
758         std::get<std::list<parser::AllocateObject>>(stmt.t)};
759     for (const auto &allocateObject : allocateObjectList) {
760       if (IsCoarrayObject(allocateObject)) {
761         return true;
762       }
763     }
764     return false;
765   }
766   bool operator()(const parser::CallStmt &stmt) {
767     const auto &procedureDesignator{
768         std::get<parser::ProcedureDesignator>(stmt.v.t)};
769     if (auto *name{std::get_if<parser::Name>(&procedureDesignator.u)}) {
770       // TODO: also ensure that the procedure is, in fact, an intrinsic
771       if (name->source == "move_alloc") {
772         const auto &args{std::get<std::list<parser::ActualArgSpec>>(stmt.v.t)};
773         if (!args.empty()) {
774           const parser::ActualArg &actualArg{
775               std::get<parser::ActualArg>(args.front().t)};
776           if (const auto *argExpr{
777                   std::get_if<common::Indirection<parser::Expr>>(
778                       &actualArg.u)}) {
779             return HasCoarray(argExpr->value());
780           }
781         }
782       }
783     }
784     return false;
785   }
786   bool operator()(const parser::Statement<parser::ActionStmt> &stmt) {
787     return std::visit(*this, stmt.statement.u);
788   }
789 
790 private:
791   bool IsCoarrayObject(const parser::AllocateObject &allocateObject) {
792     const parser::Name &name{GetLastName(allocateObject)};
793     return name.symbol && IsCoarray(*name.symbol);
794   }
795 };
796 
797 bool IsImageControlStmt(const parser::ExecutableConstruct &construct) {
798   return std::visit(ImageControlStmtHelper{}, construct.u);
799 }
800 
801 std::optional<parser::MessageFixedText> GetImageControlStmtCoarrayMsg(
802     const parser::ExecutableConstruct &construct) {
803   if (const auto *actionStmt{
804           std::get_if<parser::Statement<parser::ActionStmt>>(&construct.u)}) {
805     return std::visit(
806         common::visitors{
807             [](const common::Indirection<parser::AllocateStmt> &)
808                 -> std::optional<parser::MessageFixedText> {
809               return "ALLOCATE of a coarray is an image control"
810                      " statement"_en_US;
811             },
812             [](const common::Indirection<parser::DeallocateStmt> &)
813                 -> std::optional<parser::MessageFixedText> {
814               return "DEALLOCATE of a coarray is an image control"
815                      " statement"_en_US;
816             },
817             [](const common::Indirection<parser::CallStmt> &)
818                 -> std::optional<parser::MessageFixedText> {
819               return "MOVE_ALLOC of a coarray is an image control"
820                      " statement "_en_US;
821             },
822             [](const auto &) -> std::optional<parser::MessageFixedText> {
823               return std::nullopt;
824             },
825         },
826         actionStmt->statement.u);
827   }
828   return std::nullopt;
829 }
830 
831 parser::CharBlock GetImageControlStmtLocation(
832     const parser::ExecutableConstruct &executableConstruct) {
833   return std::visit(
834       common::visitors{
835           [](const common::Indirection<parser::ChangeTeamConstruct>
836                   &construct) {
837             return std::get<parser::Statement<parser::ChangeTeamStmt>>(
838                 construct.value().t)
839                 .source;
840           },
841           [](const common::Indirection<parser::CriticalConstruct> &construct) {
842             return std::get<parser::Statement<parser::CriticalStmt>>(
843                 construct.value().t)
844                 .source;
845           },
846           [](const parser::Statement<parser::ActionStmt> &actionStmt) {
847             return actionStmt.source;
848           },
849           [](const auto &) { return parser::CharBlock{}; },
850       },
851       executableConstruct.u);
852 }
853 
854 bool HasCoarray(const parser::Expr &expression) {
855   if (const auto *expr{GetExpr(expression)}) {
856     for (const Symbol &symbol : evaluate::CollectSymbols(*expr)) {
857       if (const Symbol * root{GetAssociationRoot(symbol)}) {
858         if (IsCoarray(*root)) {
859           return true;
860         }
861       }
862     }
863   }
864   return false;
865 }
866 
867 bool IsPolymorphic(const Symbol &symbol) {
868   if (const DeclTypeSpec * type{symbol.GetType()}) {
869     return type->IsPolymorphic();
870   }
871   return false;
872 }
873 
874 bool IsPolymorphicAllocatable(const Symbol &symbol) {
875   return IsAllocatable(symbol) && IsPolymorphic(symbol);
876 }
877 
878 std::optional<parser::MessageFormattedText> CheckAccessibleComponent(
879     const Scope &scope, const Symbol &symbol) {
880   CHECK(symbol.owner().IsDerivedType()); // symbol must be a component
881   if (symbol.attrs().test(Attr::PRIVATE)) {
882     if (const Scope * moduleScope{FindModuleContaining(symbol.owner())}) {
883       if (!moduleScope->Contains(scope)) {
884         return parser::MessageFormattedText{
885             "PRIVATE component '%s' is only accessible within module '%s'"_err_en_US,
886             symbol.name(), moduleScope->GetName().value()};
887       }
888     }
889   }
890   return std::nullopt;
891 }
892 
893 std::list<SourceName> OrderParameterNames(const Symbol &typeSymbol) {
894   std::list<SourceName> result;
895   if (const DerivedTypeSpec * spec{typeSymbol.GetParentTypeSpec()}) {
896     result = OrderParameterNames(spec->typeSymbol());
897   }
898   const auto &paramNames{typeSymbol.get<DerivedTypeDetails>().paramNames()};
899   result.insert(result.end(), paramNames.begin(), paramNames.end());
900   return result;
901 }
902 
903 SymbolVector OrderParameterDeclarations(const Symbol &typeSymbol) {
904   SymbolVector result;
905   if (const DerivedTypeSpec * spec{typeSymbol.GetParentTypeSpec()}) {
906     result = OrderParameterDeclarations(spec->typeSymbol());
907   }
908   const auto &paramDecls{typeSymbol.get<DerivedTypeDetails>().paramDecls()};
909   result.insert(result.end(), paramDecls.begin(), paramDecls.end());
910   return result;
911 }
912 
913 const DeclTypeSpec &FindOrInstantiateDerivedType(Scope &scope,
914     DerivedTypeSpec &&spec, SemanticsContext &semanticsContext,
915     DeclTypeSpec::Category category) {
916   spec.CookParameters(semanticsContext.foldingContext());
917   spec.EvaluateParameters(semanticsContext.foldingContext());
918   if (const DeclTypeSpec *
919       type{scope.FindInstantiatedDerivedType(spec, category)}) {
920     return *type;
921   }
922   // Create a new instantiation of this parameterized derived type
923   // for this particular distinct set of actual parameter values.
924   DeclTypeSpec &type{scope.MakeDerivedType(category, std::move(spec))};
925   type.derivedTypeSpec().Instantiate(scope, semanticsContext);
926   return type;
927 }
928 
929 const Symbol *FindSeparateModuleSubprogramInterface(const Symbol *proc) {
930   if (proc) {
931     if (const Symbol * submodule{proc->owner().symbol()}) {
932       if (const auto *details{submodule->detailsIf<ModuleDetails>()}) {
933         if (const Scope * ancestor{details->ancestor()}) {
934           const Symbol *iface{ancestor->FindSymbol(proc->name())};
935           if (IsSeparateModuleProcedureInterface(iface)) {
936             return iface;
937           }
938         }
939       }
940     }
941   }
942   return nullptr;
943 }
944 
945 // ComponentIterator implementation
946 
947 template <ComponentKind componentKind>
948 typename ComponentIterator<componentKind>::const_iterator
949 ComponentIterator<componentKind>::const_iterator::Create(
950     const DerivedTypeSpec &derived) {
951   const_iterator it{};
952   it.componentPath_.emplace_back(derived);
953   it.Increment(); // cue up first relevant component, if any
954   return it;
955 }
956 
957 template <ComponentKind componentKind>
958 const DerivedTypeSpec *
959 ComponentIterator<componentKind>::const_iterator::PlanComponentTraversal(
960     const Symbol &component) const {
961   if (const auto *details{component.detailsIf<ObjectEntityDetails>()}) {
962     if (const DeclTypeSpec * type{details->type()}) {
963       if (const auto *derived{type->AsDerived()}) {
964         bool traverse{false};
965         if constexpr (componentKind == ComponentKind::Ordered) {
966           // Order Component (only visit parents)
967           traverse = component.test(Symbol::Flag::ParentComp);
968         } else if constexpr (componentKind == ComponentKind::Direct) {
969           traverse = !IsAllocatableOrPointer(component);
970         } else if constexpr (componentKind == ComponentKind::Ultimate) {
971           traverse = !IsAllocatableOrPointer(component);
972         } else if constexpr (componentKind == ComponentKind::Potential) {
973           traverse = !IsPointer(component);
974         } else if constexpr (componentKind == ComponentKind::Scope) {
975           traverse = !IsAllocatableOrPointer(component);
976         }
977         if (traverse) {
978           const Symbol &newTypeSymbol{derived->typeSymbol()};
979           // Avoid infinite loop if the type is already part of the types
980           // being visited. It is possible to have "loops in type" because
981           // C744 does not forbid to use not yet declared type for
982           // ALLOCATABLE or POINTER components.
983           for (const auto &node : componentPath_) {
984             if (&newTypeSymbol == &node.GetTypeSymbol()) {
985               return nullptr;
986             }
987           }
988           return derived;
989         }
990       }
991     } // intrinsic & unlimited polymorphic not traversable
992   }
993   return nullptr;
994 }
995 
996 template <ComponentKind componentKind>
997 static bool StopAtComponentPre(const Symbol &component) {
998   if constexpr (componentKind == ComponentKind::Ordered) {
999     // Parent components need to be iterated upon after their
1000     // sub-components in structure constructor analysis.
1001     return !component.test(Symbol::Flag::ParentComp);
1002   } else if constexpr (componentKind == ComponentKind::Direct) {
1003     return true;
1004   } else if constexpr (componentKind == ComponentKind::Ultimate) {
1005     return component.has<ProcEntityDetails>() ||
1006         IsAllocatableOrPointer(component) ||
1007         (component.get<ObjectEntityDetails>().type() &&
1008             component.get<ObjectEntityDetails>().type()->AsIntrinsic());
1009   } else if constexpr (componentKind == ComponentKind::Potential) {
1010     return !IsPointer(component);
1011   }
1012 }
1013 
1014 template <ComponentKind componentKind>
1015 static bool StopAtComponentPost(const Symbol &component) {
1016   return componentKind == ComponentKind::Ordered &&
1017       component.test(Symbol::Flag::ParentComp);
1018 }
1019 
1020 template <ComponentKind componentKind>
1021 void ComponentIterator<componentKind>::const_iterator::Increment() {
1022   while (!componentPath_.empty()) {
1023     ComponentPathNode &deepest{componentPath_.back()};
1024     if (deepest.component()) {
1025       if (!deepest.descended()) {
1026         deepest.set_descended(true);
1027         if (const DerivedTypeSpec *
1028             derived{PlanComponentTraversal(*deepest.component())}) {
1029           componentPath_.emplace_back(*derived);
1030           continue;
1031         }
1032       } else if (!deepest.visited()) {
1033         deepest.set_visited(true);
1034         return; // this is the next component to visit, after descending
1035       }
1036     }
1037     auto &nameIterator{deepest.nameIterator()};
1038     if (nameIterator == deepest.nameEnd()) {
1039       componentPath_.pop_back();
1040     } else if constexpr (componentKind == ComponentKind::Scope) {
1041       deepest.set_component(*nameIterator++->second);
1042       deepest.set_descended(false);
1043       deepest.set_visited(true);
1044       return; // this is the next component to visit, before descending
1045     } else {
1046       const Scope &scope{deepest.GetScope()};
1047       auto scopeIter{scope.find(*nameIterator++)};
1048       if (scopeIter != scope.cend()) {
1049         const Symbol &component{*scopeIter->second};
1050         deepest.set_component(component);
1051         deepest.set_descended(false);
1052         if (StopAtComponentPre<componentKind>(component)) {
1053           deepest.set_visited(true);
1054           return; // this is the next component to visit, before descending
1055         } else {
1056           deepest.set_visited(!StopAtComponentPost<componentKind>(component));
1057         }
1058       }
1059     }
1060   }
1061 }
1062 
1063 template <ComponentKind componentKind>
1064 std::string
1065 ComponentIterator<componentKind>::const_iterator::BuildResultDesignatorName()
1066     const {
1067   std::string designator{""};
1068   for (const auto &node : componentPath_) {
1069     designator += "%" + DEREF(node.component()).name().ToString();
1070   }
1071   return designator;
1072 }
1073 
1074 template class ComponentIterator<ComponentKind::Ordered>;
1075 template class ComponentIterator<ComponentKind::Direct>;
1076 template class ComponentIterator<ComponentKind::Ultimate>;
1077 template class ComponentIterator<ComponentKind::Potential>;
1078 template class ComponentIterator<ComponentKind::Scope>;
1079 
1080 UltimateComponentIterator::const_iterator FindCoarrayUltimateComponent(
1081     const DerivedTypeSpec &derived) {
1082   UltimateComponentIterator ultimates{derived};
1083   return std::find_if(ultimates.begin(), ultimates.end(), IsCoarray);
1084 }
1085 
1086 UltimateComponentIterator::const_iterator FindPointerUltimateComponent(
1087     const DerivedTypeSpec &derived) {
1088   UltimateComponentIterator ultimates{derived};
1089   return std::find_if(ultimates.begin(), ultimates.end(), IsPointer);
1090 }
1091 
1092 PotentialComponentIterator::const_iterator FindEventOrLockPotentialComponent(
1093     const DerivedTypeSpec &derived) {
1094   PotentialComponentIterator potentials{derived};
1095   return std::find_if(
1096       potentials.begin(), potentials.end(), [](const Symbol &component) {
1097         if (const auto *details{component.detailsIf<ObjectEntityDetails>()}) {
1098           const DeclTypeSpec *type{details->type()};
1099           return type && IsEventTypeOrLockType(type->AsDerived());
1100         }
1101         return false;
1102       });
1103 }
1104 
1105 UltimateComponentIterator::const_iterator FindAllocatableUltimateComponent(
1106     const DerivedTypeSpec &derived) {
1107   UltimateComponentIterator ultimates{derived};
1108   return std::find_if(ultimates.begin(), ultimates.end(), IsAllocatable);
1109 }
1110 
1111 UltimateComponentIterator::const_iterator
1112 FindPolymorphicAllocatableUltimateComponent(const DerivedTypeSpec &derived) {
1113   UltimateComponentIterator ultimates{derived};
1114   return std::find_if(
1115       ultimates.begin(), ultimates.end(), IsPolymorphicAllocatable);
1116 }
1117 
1118 UltimateComponentIterator::const_iterator
1119 FindPolymorphicAllocatableNonCoarrayUltimateComponent(
1120     const DerivedTypeSpec &derived) {
1121   UltimateComponentIterator ultimates{derived};
1122   return std::find_if(ultimates.begin(), ultimates.end(), [](const Symbol &x) {
1123     return IsPolymorphicAllocatable(x) && !IsCoarray(x);
1124   });
1125 }
1126 
1127 const Symbol *FindUltimateComponent(const DerivedTypeSpec &derived,
1128     const std::function<bool(const Symbol &)> &predicate) {
1129   UltimateComponentIterator ultimates{derived};
1130   if (auto it{std::find_if(ultimates.begin(), ultimates.end(),
1131           [&predicate](const Symbol &component) -> bool {
1132             return predicate(component);
1133           })}) {
1134     return &*it;
1135   }
1136   return nullptr;
1137 }
1138 
1139 const Symbol *FindUltimateComponent(const Symbol &symbol,
1140     const std::function<bool(const Symbol &)> &predicate) {
1141   if (predicate(symbol)) {
1142     return &symbol;
1143   } else if (const auto *object{symbol.detailsIf<ObjectEntityDetails>()}) {
1144     if (const auto *type{object->type()}) {
1145       if (const auto *derived{type->AsDerived()}) {
1146         return FindUltimateComponent(*derived, predicate);
1147       }
1148     }
1149   }
1150   return nullptr;
1151 }
1152 
1153 const Symbol *FindImmediateComponent(const DerivedTypeSpec &type,
1154     const std::function<bool(const Symbol &)> &predicate) {
1155   if (const Scope * scope{type.scope()}) {
1156     const Symbol *parent{nullptr};
1157     for (const auto &pair : *scope) {
1158       const Symbol *symbol{&*pair.second};
1159       if (predicate(*symbol)) {
1160         return symbol;
1161       }
1162       if (symbol->test(Symbol::Flag::ParentComp)) {
1163         parent = symbol;
1164       }
1165     }
1166     if (parent) {
1167       if (const auto *object{parent->detailsIf<ObjectEntityDetails>()}) {
1168         if (const auto *type{object->type()}) {
1169           if (const auto *derived{type->AsDerived()}) {
1170             return FindImmediateComponent(*derived, predicate);
1171           }
1172         }
1173       }
1174     }
1175   }
1176   return nullptr;
1177 }
1178 
1179 bool IsFunctionResult(const Symbol &symbol) {
1180   return (symbol.has<ObjectEntityDetails>() &&
1181              symbol.get<ObjectEntityDetails>().isFuncResult()) ||
1182       (symbol.has<ProcEntityDetails>() &&
1183           symbol.get<ProcEntityDetails>().isFuncResult());
1184 }
1185 
1186 bool IsFunctionResultWithSameNameAsFunction(const Symbol &symbol) {
1187   if (IsFunctionResult(symbol)) {
1188     if (const Symbol * function{symbol.owner().symbol()}) {
1189       return symbol.name() == function->name();
1190     }
1191   }
1192   return false;
1193 }
1194 
1195 void LabelEnforce::Post(const parser::GotoStmt &gotoStmt) {
1196   checkLabelUse(gotoStmt.v);
1197 }
1198 void LabelEnforce::Post(const parser::ComputedGotoStmt &computedGotoStmt) {
1199   for (auto &i : std::get<std::list<parser::Label>>(computedGotoStmt.t)) {
1200     checkLabelUse(i);
1201   }
1202 }
1203 
1204 void LabelEnforce::Post(const parser::ArithmeticIfStmt &arithmeticIfStmt) {
1205   checkLabelUse(std::get<1>(arithmeticIfStmt.t));
1206   checkLabelUse(std::get<2>(arithmeticIfStmt.t));
1207   checkLabelUse(std::get<3>(arithmeticIfStmt.t));
1208 }
1209 
1210 void LabelEnforce::Post(const parser::AssignStmt &assignStmt) {
1211   checkLabelUse(std::get<parser::Label>(assignStmt.t));
1212 }
1213 
1214 void LabelEnforce::Post(const parser::AssignedGotoStmt &assignedGotoStmt) {
1215   for (auto &i : std::get<std::list<parser::Label>>(assignedGotoStmt.t)) {
1216     checkLabelUse(i);
1217   }
1218 }
1219 
1220 void LabelEnforce::Post(const parser::AltReturnSpec &altReturnSpec) {
1221   checkLabelUse(altReturnSpec.v);
1222 }
1223 
1224 void LabelEnforce::Post(const parser::ErrLabel &errLabel) {
1225   checkLabelUse(errLabel.v);
1226 }
1227 void LabelEnforce::Post(const parser::EndLabel &endLabel) {
1228   checkLabelUse(endLabel.v);
1229 }
1230 void LabelEnforce::Post(const parser::EorLabel &eorLabel) {
1231   checkLabelUse(eorLabel.v);
1232 }
1233 
1234 void LabelEnforce::checkLabelUse(const parser::Label &labelUsed) {
1235   if (labels_.find(labelUsed) == labels_.end()) {
1236     SayWithConstruct(context_, currentStatementSourcePosition_,
1237         parser::MessageFormattedText{
1238             "Control flow escapes from %s"_err_en_US, construct_},
1239         constructSourcePosition_);
1240   }
1241 }
1242 
1243 parser::MessageFormattedText LabelEnforce::GetEnclosingConstructMsg() {
1244   return {"Enclosing %s statement"_en_US, construct_};
1245 }
1246 
1247 void LabelEnforce::SayWithConstruct(SemanticsContext &context,
1248     parser::CharBlock stmtLocation, parser::MessageFormattedText &&message,
1249     parser::CharBlock constructLocation) {
1250   context.Say(stmtLocation, message)
1251       .Attach(constructLocation, GetEnclosingConstructMsg());
1252 }
1253 
1254 } // namespace Fortran::semantics
1255