1 //===-- lib/Semantics/data-to-inits.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 // DATA statement object/value checking and conversion to static
10 // initializers
11 // - Applies specific checks to each scalar element initialization with a
12 // constant value or pointer target with class DataInitializationCompiler;
13 // - Collects the elemental initializations for each symbol and converts them
14 // into a single init() expression with member function
15 // DataChecker::ConstructInitializer().
16
17 #include "data-to-inits.h"
18 #include "pointer-assignment.h"
19 #include "flang/Evaluate/fold-designator.h"
20 #include "flang/Semantics/tools.h"
21
22 namespace Fortran::semantics {
23
24 // Steps through a list of values in a DATA statement set; implements
25 // repetition.
26 class ValueListIterator {
27 public:
ValueListIterator(const parser::DataStmtSet & set)28 explicit ValueListIterator(const parser::DataStmtSet &set)
29 : end_{std::get<std::list<parser::DataStmtValue>>(set.t).end()},
30 at_{std::get<std::list<parser::DataStmtValue>>(set.t).begin()} {
31 SetRepetitionCount();
32 }
hasFatalError() const33 bool hasFatalError() const { return hasFatalError_; }
IsAtEnd() const34 bool IsAtEnd() const { return at_ == end_; }
operator *() const35 const SomeExpr *operator*() const { return GetExpr(GetConstant()); }
LocateSource() const36 parser::CharBlock LocateSource() const { return GetConstant().source; }
operator ++()37 ValueListIterator &operator++() {
38 if (repetitionsRemaining_ > 0) {
39 --repetitionsRemaining_;
40 } else if (at_ != end_) {
41 ++at_;
42 SetRepetitionCount();
43 }
44 return *this;
45 }
46
47 private:
48 using listIterator = std::list<parser::DataStmtValue>::const_iterator;
49 void SetRepetitionCount();
GetConstant() const50 const parser::DataStmtConstant &GetConstant() const {
51 return std::get<parser::DataStmtConstant>(at_->t);
52 }
53
54 listIterator end_;
55 listIterator at_;
56 ConstantSubscript repetitionsRemaining_{0};
57 bool hasFatalError_{false};
58 };
59
SetRepetitionCount()60 void ValueListIterator::SetRepetitionCount() {
61 for (repetitionsRemaining_ = 1; at_ != end_; ++at_) {
62 if (at_->repetitions < 0) {
63 hasFatalError_ = true;
64 }
65 if (at_->repetitions > 0) {
66 repetitionsRemaining_ = at_->repetitions - 1;
67 return;
68 }
69 }
70 repetitionsRemaining_ = 0;
71 }
72
73 // Collects all of the elemental initializations from DATA statements
74 // into a single image for each symbol that appears in any DATA.
75 // Expands the implied DO loops and array references.
76 // Applies checks that validate each distinct elemental initialization
77 // of the variables in a data-stmt-set, as well as those that apply
78 // to the corresponding values being use to initialize each element.
79 class DataInitializationCompiler {
80 public:
DataInitializationCompiler(DataInitializations & inits,evaluate::ExpressionAnalyzer & a,const parser::DataStmtSet & set)81 DataInitializationCompiler(DataInitializations &inits,
82 evaluate::ExpressionAnalyzer &a, const parser::DataStmtSet &set)
83 : inits_{inits}, exprAnalyzer_{a}, values_{set} {}
inits() const84 const DataInitializations &inits() const { return inits_; }
HasSurplusValues() const85 bool HasSurplusValues() const { return !values_.IsAtEnd(); }
86 bool Scan(const parser::DataStmtObject &);
87
88 private:
89 bool Scan(const parser::Variable &);
90 bool Scan(const parser::Designator &);
91 bool Scan(const parser::DataImpliedDo &);
92 bool Scan(const parser::DataIDoObject &);
93
94 // Initializes all elements of a designator, which can be an array or section.
95 bool InitDesignator(const SomeExpr &);
96 // Initializes a single object.
97 bool InitElement(const evaluate::OffsetSymbol &, const SomeExpr &designator);
98 // If the returned flag is true, emit a warning about CHARACTER misusage.
99 std::optional<std::pair<SomeExpr, bool>> ConvertElement(
100 const SomeExpr &, const evaluate::DynamicType &);
101
102 DataInitializations &inits_;
103 evaluate::ExpressionAnalyzer &exprAnalyzer_;
104 ValueListIterator values_;
105 };
106
Scan(const parser::DataStmtObject & object)107 bool DataInitializationCompiler::Scan(const parser::DataStmtObject &object) {
108 return std::visit(
109 common::visitors{
110 [&](const common::Indirection<parser::Variable> &var) {
111 return Scan(var.value());
112 },
113 [&](const parser::DataImpliedDo &ido) { return Scan(ido); },
114 },
115 object.u);
116 }
117
Scan(const parser::Variable & var)118 bool DataInitializationCompiler::Scan(const parser::Variable &var) {
119 if (const auto *expr{GetExpr(var)}) {
120 exprAnalyzer_.GetFoldingContext().messages().SetLocation(var.GetSource());
121 if (InitDesignator(*expr)) {
122 return true;
123 }
124 }
125 return false;
126 }
127
Scan(const parser::Designator & designator)128 bool DataInitializationCompiler::Scan(const parser::Designator &designator) {
129 if (auto expr{exprAnalyzer_.Analyze(designator)}) {
130 exprAnalyzer_.GetFoldingContext().messages().SetLocation(
131 parser::FindSourceLocation(designator));
132 if (InitDesignator(*expr)) {
133 return true;
134 }
135 }
136 return false;
137 }
138
Scan(const parser::DataImpliedDo & ido)139 bool DataInitializationCompiler::Scan(const parser::DataImpliedDo &ido) {
140 const auto &bounds{std::get<parser::DataImpliedDo::Bounds>(ido.t)};
141 auto name{bounds.name.thing.thing};
142 const auto *lowerExpr{GetExpr(bounds.lower.thing.thing)};
143 const auto *upperExpr{GetExpr(bounds.upper.thing.thing)};
144 const auto *stepExpr{
145 bounds.step ? GetExpr(bounds.step->thing.thing) : nullptr};
146 if (lowerExpr && upperExpr) {
147 auto lower{ToInt64(*lowerExpr)};
148 auto upper{ToInt64(*upperExpr)};
149 auto step{stepExpr ? ToInt64(*stepExpr) : std::nullopt};
150 auto stepVal{step.value_or(1)};
151 if (stepVal == 0) {
152 exprAnalyzer_.Say(name.source,
153 "DATA statement implied DO loop has a step value of zero"_err_en_US);
154 } else if (lower && upper) {
155 int kind{evaluate::ResultType<evaluate::ImpliedDoIndex>::kind};
156 if (const auto dynamicType{evaluate::DynamicType::From(*name.symbol)}) {
157 if (dynamicType->category() == TypeCategory::Integer) {
158 kind = dynamicType->kind();
159 }
160 }
161 if (exprAnalyzer_.AddImpliedDo(name.source, kind)) {
162 auto &value{exprAnalyzer_.GetFoldingContext().StartImpliedDo(
163 name.source, *lower)};
164 bool result{true};
165 for (auto n{(*upper - value + stepVal) / stepVal}; n > 0;
166 --n, value += stepVal) {
167 for (const auto &object :
168 std::get<std::list<parser::DataIDoObject>>(ido.t)) {
169 if (!Scan(object)) {
170 result = false;
171 break;
172 }
173 }
174 }
175 exprAnalyzer_.GetFoldingContext().EndImpliedDo(name.source);
176 exprAnalyzer_.RemoveImpliedDo(name.source);
177 return result;
178 }
179 }
180 }
181 return false;
182 }
183
Scan(const parser::DataIDoObject & object)184 bool DataInitializationCompiler::Scan(const parser::DataIDoObject &object) {
185 return std::visit(
186 common::visitors{
187 [&](const parser::Scalar<common::Indirection<parser::Designator>>
188 &var) { return Scan(var.thing.value()); },
189 [&](const common::Indirection<parser::DataImpliedDo> &ido) {
190 return Scan(ido.value());
191 },
192 },
193 object.u);
194 }
195
InitDesignator(const SomeExpr & designator)196 bool DataInitializationCompiler::InitDesignator(const SomeExpr &designator) {
197 evaluate::FoldingContext &context{exprAnalyzer_.GetFoldingContext()};
198 evaluate::DesignatorFolder folder{context};
199 while (auto offsetSymbol{folder.FoldDesignator(designator)}) {
200 if (folder.isOutOfRange()) {
201 if (auto bad{evaluate::OffsetToDesignator(context, *offsetSymbol)}) {
202 exprAnalyzer_.context().Say(
203 "DATA statement designator '%s' is out of range"_err_en_US,
204 bad->AsFortran());
205 } else {
206 exprAnalyzer_.context().Say(
207 "DATA statement designator '%s' is out of range"_err_en_US,
208 designator.AsFortran());
209 }
210 return false;
211 } else if (!InitElement(*offsetSymbol, designator)) {
212 return false;
213 } else {
214 ++values_;
215 }
216 }
217 return folder.isEmpty();
218 }
219
220 std::optional<std::pair<SomeExpr, bool>>
ConvertElement(const SomeExpr & expr,const evaluate::DynamicType & type)221 DataInitializationCompiler::ConvertElement(
222 const SomeExpr &expr, const evaluate::DynamicType &type) {
223 if (auto converted{evaluate::ConvertToType(type, SomeExpr{expr})}) {
224 return {std::make_pair(std::move(*converted), false)};
225 }
226 if (std::optional<std::string> chValue{evaluate::GetScalarConstantValue<
227 evaluate::Type<TypeCategory::Character, 1>>(expr)}) {
228 // Allow DATA initialization with Hollerith and kind=1 CHARACTER like
229 // (most) other Fortran compilers do. Pad on the right with spaces
230 // when short, truncate the right if long.
231 // TODO: big-endian targets
232 std::size_t bytes{static_cast<std::size_t>(evaluate::ToInt64(
233 type.MeasureSizeInBytes(&exprAnalyzer_.GetFoldingContext()))
234 .value())};
235 evaluate::BOZLiteralConstant bits{0};
236 for (std::size_t j{0}; j < bytes; ++j) {
237 char ch{j >= chValue->size() ? ' ' : chValue->at(j)};
238 evaluate::BOZLiteralConstant chBOZ{static_cast<unsigned char>(ch)};
239 bits = bits.IOR(chBOZ.SHIFTL(8 * j));
240 }
241 if (auto converted{evaluate::ConvertToType(type, SomeExpr{bits})}) {
242 return {std::make_pair(std::move(*converted), true)};
243 }
244 }
245 return std::nullopt;
246 }
247
InitElement(const evaluate::OffsetSymbol & offsetSymbol,const SomeExpr & designator)248 bool DataInitializationCompiler::InitElement(
249 const evaluate::OffsetSymbol &offsetSymbol, const SomeExpr &designator) {
250 const Symbol &symbol{offsetSymbol.symbol()};
251 const Symbol *lastSymbol{GetLastSymbol(designator)};
252 bool isPointer{lastSymbol && IsPointer(*lastSymbol)};
253 bool isProcPointer{lastSymbol && IsProcedurePointer(*lastSymbol)};
254 evaluate::FoldingContext &context{exprAnalyzer_.GetFoldingContext()};
255 auto restorer{context.messages().SetLocation(values_.LocateSource())};
256
257 const auto DescribeElement{[&]() {
258 if (auto badDesignator{
259 evaluate::OffsetToDesignator(context, offsetSymbol)}) {
260 return badDesignator->AsFortran();
261 } else {
262 // Error recovery
263 std::string buf;
264 llvm::raw_string_ostream ss{buf};
265 ss << offsetSymbol.symbol().name() << " offset " << offsetSymbol.offset()
266 << " bytes for " << offsetSymbol.size() << " bytes";
267 return ss.str();
268 }
269 }};
270 const auto GetImage{[&]() -> evaluate::InitialImage & {
271 auto &symbolInit{inits_.emplace(&symbol, symbol.size()).first->second};
272 symbolInit.inits.emplace_back(offsetSymbol.offset(), offsetSymbol.size());
273 return symbolInit.image;
274 }};
275 const auto OutOfRangeError{[&]() {
276 evaluate::AttachDeclaration(
277 exprAnalyzer_.context().Say(
278 "DATA statement designator '%s' is out of range for its variable '%s'"_err_en_US,
279 DescribeElement(), symbol.name()),
280 symbol);
281 }};
282
283 if (values_.hasFatalError()) {
284 return false;
285 } else if (values_.IsAtEnd()) {
286 exprAnalyzer_.context().Say(
287 "DATA statement set has no value for '%s'"_err_en_US,
288 DescribeElement());
289 return false;
290 } else if (static_cast<std::size_t>(
291 offsetSymbol.offset() + offsetSymbol.size()) > symbol.size()) {
292 OutOfRangeError();
293 return false;
294 }
295
296 const SomeExpr *expr{*values_};
297 if (!expr) {
298 CHECK(exprAnalyzer_.context().AnyFatalError());
299 } else if (isPointer) {
300 if (static_cast<std::size_t>(offsetSymbol.offset() + offsetSymbol.size()) >
301 symbol.size()) {
302 OutOfRangeError();
303 } else if (evaluate::IsNullPointer(*expr)) {
304 // nothing to do; rely on zero initialization
305 return true;
306 } else if (isProcPointer) {
307 if (evaluate::IsProcedure(*expr)) {
308 if (CheckPointerAssignment(context, designator, *expr)) {
309 GetImage().AddPointer(offsetSymbol.offset(), *expr);
310 return true;
311 }
312 } else {
313 exprAnalyzer_.Say(
314 "Data object '%s' may not be used to initialize '%s', which is a procedure pointer"_err_en_US,
315 expr->AsFortran(), DescribeElement());
316 }
317 } else if (evaluate::IsProcedure(*expr)) {
318 exprAnalyzer_.Say(
319 "Procedure '%s' may not be used to initialize '%s', which is not a procedure pointer"_err_en_US,
320 expr->AsFortran(), DescribeElement());
321 } else if (CheckInitialTarget(context, designator, *expr)) {
322 GetImage().AddPointer(offsetSymbol.offset(), *expr);
323 return true;
324 }
325 } else if (evaluate::IsNullPointer(*expr)) {
326 exprAnalyzer_.Say("Initializer for '%s' must not be a pointer"_err_en_US,
327 DescribeElement());
328 } else if (evaluate::IsProcedure(*expr)) {
329 exprAnalyzer_.Say("Initializer for '%s' must not be a procedure"_err_en_US,
330 DescribeElement());
331 } else if (auto designatorType{designator.GetType()}) {
332 if (auto converted{ConvertElement(*expr, *designatorType)}) {
333 // value non-pointer initialization
334 if (std::holds_alternative<evaluate::BOZLiteralConstant>(expr->u) &&
335 designatorType->category() != TypeCategory::Integer) { // 8.6.7(11)
336 exprAnalyzer_.Say(
337 "BOZ literal should appear in a DATA statement only as a value for an integer object, but '%s' is '%s'"_en_US,
338 DescribeElement(), designatorType->AsFortran());
339 } else if (converted->second) {
340 exprAnalyzer_.context().Say(
341 "DATA statement value initializes '%s' of type '%s' with CHARACTER"_en_US,
342 DescribeElement(), designatorType->AsFortran());
343 }
344 auto folded{evaluate::Fold(context, std::move(converted->first))};
345 switch (
346 GetImage().Add(offsetSymbol.offset(), offsetSymbol.size(), folded)) {
347 case evaluate::InitialImage::Ok:
348 return true;
349 case evaluate::InitialImage::NotAConstant:
350 exprAnalyzer_.Say(
351 "DATA statement value '%s' for '%s' is not a constant"_err_en_US,
352 folded.AsFortran(), DescribeElement());
353 break;
354 case evaluate::InitialImage::OutOfRange:
355 OutOfRangeError();
356 break;
357 default:
358 CHECK(exprAnalyzer_.context().AnyFatalError());
359 break;
360 }
361 } else {
362 exprAnalyzer_.context().Say(
363 "DATA statement value could not be converted to the type '%s' of the object '%s'"_err_en_US,
364 designatorType->AsFortran(), DescribeElement());
365 }
366 } else {
367 CHECK(exprAnalyzer_.context().AnyFatalError());
368 }
369 return false;
370 }
371
AccumulateDataInitializations(DataInitializations & inits,evaluate::ExpressionAnalyzer & exprAnalyzer,const parser::DataStmtSet & set)372 void AccumulateDataInitializations(DataInitializations &inits,
373 evaluate::ExpressionAnalyzer &exprAnalyzer,
374 const parser::DataStmtSet &set) {
375 DataInitializationCompiler scanner{inits, exprAnalyzer, set};
376 for (const auto &object :
377 std::get<std::list<parser::DataStmtObject>>(set.t)) {
378 if (!scanner.Scan(object)) {
379 return;
380 }
381 }
382 if (scanner.HasSurplusValues()) {
383 exprAnalyzer.context().Say(
384 "DATA statement set has more values than objects"_err_en_US);
385 }
386 }
387
CombineSomeEquivalencedInits(DataInitializations & inits,evaluate::ExpressionAnalyzer & exprAnalyzer)388 static bool CombineSomeEquivalencedInits(
389 DataInitializations &inits, evaluate::ExpressionAnalyzer &exprAnalyzer) {
390 auto end{inits.end()};
391 for (auto iter{inits.begin()}; iter != end; ++iter) {
392 const Symbol &symbol{*iter->first};
393 Scope &scope{const_cast<Scope &>(symbol.owner())};
394 if (scope.equivalenceSets().empty()) {
395 continue; // no problem to solve here
396 }
397 const auto *commonBlock{FindCommonBlockContaining(symbol)};
398 // Sweep following DATA initializations in search of overlapping
399 // objects, accumulating into a vector; iterate to a fixed point.
400 std::vector<const Symbol *> conflicts;
401 auto minStart{symbol.offset()};
402 auto maxEnd{symbol.offset() + symbol.size()};
403 std::size_t minElementBytes{1};
404 while (true) {
405 auto prevCount{conflicts.size()};
406 conflicts.clear();
407 for (auto scan{iter}; ++scan != end;) {
408 const Symbol &other{*scan->first};
409 const Scope &otherScope{other.owner()};
410 if (&otherScope == &scope &&
411 FindCommonBlockContaining(other) == commonBlock &&
412 maxEnd > other.offset() &&
413 other.offset() + other.size() > minStart) {
414 // "other" conflicts with "symbol" or another conflict
415 conflicts.push_back(&other);
416 minStart = std::min(minStart, other.offset());
417 maxEnd = std::max(maxEnd, other.offset() + other.size());
418 }
419 }
420 if (conflicts.size() == prevCount) {
421 break;
422 }
423 }
424 if (conflicts.empty()) {
425 continue;
426 }
427 // Compute the minimum common granularity
428 if (auto dyType{evaluate::DynamicType::From(symbol)}) {
429 minElementBytes = evaluate::ToInt64(
430 dyType->MeasureSizeInBytes(&exprAnalyzer.GetFoldingContext()))
431 .value_or(1);
432 }
433 for (const Symbol *s : conflicts) {
434 if (auto dyType{evaluate::DynamicType::From(*s)}) {
435 minElementBytes = std::min(minElementBytes,
436 static_cast<std::size_t>(evaluate::ToInt64(
437 dyType->MeasureSizeInBytes(&exprAnalyzer.GetFoldingContext()))
438 .value_or(1)));
439 } else {
440 minElementBytes = 1;
441 }
442 }
443 CHECK(minElementBytes > 0);
444 CHECK((minElementBytes & (minElementBytes - 1)) == 0);
445 auto bytes{static_cast<common::ConstantSubscript>(maxEnd - minStart)};
446 CHECK(bytes % minElementBytes == 0);
447 const DeclTypeSpec &typeSpec{scope.MakeNumericType(
448 TypeCategory::Integer, KindExpr{minElementBytes})};
449 // Combine "symbol" and "conflicts[]" into a compiler array temp
450 // that overlaps all of them, and merge their initial values into
451 // the temp's initializer.
452 SourceName name{exprAnalyzer.context().GetTempName(scope)};
453 auto emplaced{
454 scope.try_emplace(name, Attrs{Attr::SAVE}, ObjectEntityDetails{})};
455 CHECK(emplaced.second);
456 Symbol &combinedSymbol{*emplaced.first->second};
457 auto &details{combinedSymbol.get<ObjectEntityDetails>()};
458 combinedSymbol.set_offset(minStart);
459 combinedSymbol.set_size(bytes);
460 details.set_type(typeSpec);
461 ArraySpec arraySpec;
462 arraySpec.emplace_back(ShapeSpec::MakeExplicit(Bound{
463 bytes / static_cast<common::ConstantSubscript>(minElementBytes)}));
464 details.set_shape(arraySpec);
465 if (commonBlock) {
466 details.set_commonBlock(*commonBlock);
467 }
468 // Merge these EQUIVALENCE'd DATA initializations, and remove the
469 // original initializations from the map.
470 auto combinedInit{
471 inits.emplace(&combinedSymbol, static_cast<std::size_t>(bytes))};
472 evaluate::InitialImage &combined{combinedInit.first->second.image};
473 combined.Incorporate(symbol.offset() - minStart, iter->second.image);
474 inits.erase(iter);
475 for (const Symbol *s : conflicts) {
476 auto sIter{inits.find(s)};
477 CHECK(sIter != inits.end());
478 combined.Incorporate(s->offset() - minStart, sIter->second.image);
479 inits.erase(sIter);
480 }
481 return true; // got one
482 }
483 return false; // no remaining EQUIVALENCE'd DATA initializations
484 }
485
486 // Converts the initialization image for all the DATA statement appearances of
487 // a single symbol into an init() expression in the symbol table entry.
ConstructInitializer(const Symbol & symbol,SymbolDataInitialization & initialization,evaluate::ExpressionAnalyzer & exprAnalyzer)488 void ConstructInitializer(const Symbol &symbol,
489 SymbolDataInitialization &initialization,
490 evaluate::ExpressionAnalyzer &exprAnalyzer) {
491 auto &context{exprAnalyzer.GetFoldingContext()};
492 initialization.inits.sort();
493 ConstantSubscript next{0};
494 for (const auto &init : initialization.inits) {
495 if (init.start() < next) {
496 auto badDesignator{evaluate::OffsetToDesignator(
497 context, symbol, init.start(), init.size())};
498 CHECK(badDesignator);
499 exprAnalyzer.Say(symbol.name(),
500 "DATA statement initializations affect '%s' more than once"_err_en_US,
501 badDesignator->AsFortran());
502 }
503 next = init.start() + init.size();
504 CHECK(next <= static_cast<ConstantSubscript>(initialization.image.size()));
505 }
506 if (const auto *proc{symbol.detailsIf<ProcEntityDetails>()}) {
507 CHECK(IsProcedurePointer(symbol));
508 const auto &procDesignator{initialization.image.AsConstantProcPointer()};
509 CHECK(!procDesignator.GetComponent());
510 auto &mutableProc{const_cast<ProcEntityDetails &>(*proc)};
511 mutableProc.set_init(DEREF(procDesignator.GetSymbol()));
512 } else if (const auto *object{symbol.detailsIf<ObjectEntityDetails>()}) {
513 if (auto symbolType{evaluate::DynamicType::From(symbol)}) {
514 auto &mutableObject{const_cast<ObjectEntityDetails &>(*object)};
515 if (IsPointer(symbol)) {
516 mutableObject.set_init(
517 initialization.image.AsConstantDataPointer(*symbolType));
518 } else {
519 if (auto extents{evaluate::GetConstantExtents(context, symbol)}) {
520 mutableObject.set_init(
521 initialization.image.AsConstant(context, *symbolType, *extents));
522 } else {
523 exprAnalyzer.Say(symbol.name(),
524 "internal: unknown shape for '%s' while constructing initializer from DATA"_err_en_US,
525 symbol.name());
526 return;
527 }
528 }
529 } else {
530 exprAnalyzer.Say(symbol.name(),
531 "internal: no type for '%s' while constructing initializer from DATA"_err_en_US,
532 symbol.name());
533 return;
534 }
535 if (!object->init()) {
536 exprAnalyzer.Say(symbol.name(),
537 "internal: could not construct an initializer from DATA statements for '%s'"_err_en_US,
538 symbol.name());
539 }
540 } else {
541 CHECK(exprAnalyzer.context().AnyFatalError());
542 }
543 }
544
ConvertToInitializers(DataInitializations & inits,evaluate::ExpressionAnalyzer & exprAnalyzer)545 void ConvertToInitializers(
546 DataInitializations &inits, evaluate::ExpressionAnalyzer &exprAnalyzer) {
547 while (CombineSomeEquivalencedInits(inits, exprAnalyzer)) {
548 }
549 for (auto &[symbolPtr, initialization] : inits) {
550 ConstructInitializer(*symbolPtr, initialization, exprAnalyzer);
551 }
552 }
553 } // namespace Fortran::semantics
554