xref: /llvm-project/flang/lib/Semantics/assignment.cpp (revision a40dbe7c41c6f1bcddc087752eef004f4c1d093e)
1 //===-- lib/Semantics/assignment.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 "assignment.h"
10 #include "pointer-assignment.h"
11 #include "flang/Common/idioms.h"
12 #include "flang/Common/restorer.h"
13 #include "flang/Evaluate/characteristics.h"
14 #include "flang/Evaluate/expression.h"
15 #include "flang/Evaluate/fold.h"
16 #include "flang/Evaluate/tools.h"
17 #include "flang/Parser/message.h"
18 #include "flang/Parser/parse-tree-visitor.h"
19 #include "flang/Parser/parse-tree.h"
20 #include "flang/Semantics/expression.h"
21 #include "flang/Semantics/symbol.h"
22 #include "flang/Semantics/tools.h"
23 #include <optional>
24 #include <set>
25 #include <string>
26 #include <type_traits>
27 
28 using namespace Fortran::parser::literals;
29 
30 namespace Fortran::semantics {
31 
32 class AssignmentContext {
33 public:
34   explicit AssignmentContext(SemanticsContext &context) : context_{context} {}
35   AssignmentContext(AssignmentContext &&) = default;
36   AssignmentContext(const AssignmentContext &) = delete;
37   bool operator==(const AssignmentContext &x) const { return this == &x; }
38 
39   template<typename A> void PushWhereContext(const A &);
40   void PopWhereContext();
41   void Analyze(const parser::AssignmentStmt &);
42   void Analyze(const parser::PointerAssignmentStmt &);
43   void Analyze(const parser::ConcurrentControl &);
44 
45 private:
46   bool CheckForPureContext(const SomeExpr &lhs, const SomeExpr &rhs,
47       parser::CharBlock rhsSource, bool isPointerAssignment);
48   void CheckShape(parser::CharBlock, const SomeExpr *);
49   template<typename... A>
50   parser::Message *Say(parser::CharBlock at, A &&... args) {
51     return &context_.Say(at, std::forward<A>(args)...);
52   }
53   evaluate::FoldingContext &foldingContext() {
54     return context_.foldingContext();
55   }
56 
57   SemanticsContext &context_;
58   int whereDepth_{0};  // number of WHEREs currently nested in
59   // shape of masks in LHS of assignments in current WHERE:
60   std::vector<std::optional<std::int64_t>> whereExtents_;
61 };
62 
63 void AssignmentContext::Analyze(const parser::AssignmentStmt &stmt) {
64   if (const evaluate::Assignment * assignment{GetAssignment(stmt)}) {
65     const SomeExpr &lhs{assignment->lhs};
66     const SomeExpr &rhs{assignment->rhs};
67     auto lhsLoc{std::get<parser::Variable>(stmt.t).GetSource()};
68     auto rhsLoc{std::get<parser::Expr>(stmt.t).source};
69     auto shape{evaluate::GetShape(foldingContext(), lhs)};
70     if (shape && !shape->empty() && !shape->back().has_value()) {  // C1014
71       Say(lhsLoc,
72           "Left-hand side of assignment may not be a whole assumed-size array"_err_en_US);
73     }
74     if (CheckForPureContext(lhs, rhs, rhsLoc, false)) {
75       const Scope &scope{context_.FindScope(lhsLoc)};
76       if (auto whyNot{WhyNotModifiable(lhsLoc, lhs, scope, true)}) {
77         if (auto *msg{Say(lhsLoc,
78                 "Left-hand side of assignment is not modifiable"_err_en_US)}) {
79           msg->Attach(*whyNot);
80         }
81       }
82     }
83     if (whereDepth_ > 0) {
84       CheckShape(lhsLoc, &lhs);
85     }
86   }
87 }
88 
89 void AssignmentContext::Analyze(const parser::PointerAssignmentStmt &stmt) {
90   CHECK(whereDepth_ == 0);
91   if (const evaluate::Assignment * assignment{GetAssignment(stmt)}) {
92     const SomeExpr &lhs{assignment->lhs};
93     const SomeExpr &rhs{assignment->rhs};
94     CheckForPureContext(lhs, rhs, std::get<parser::Expr>(stmt.t).source, true);
95     auto restorer{
96         foldingContext().messages().SetLocation(context_.location().value())};
97     CheckPointerAssignment(foldingContext(), *assignment);
98   }
99 }
100 
101 // C1594 checks
102 static bool IsPointerDummyOfPureFunction(const Symbol &x) {
103   return IsPointerDummy(x) && FindPureProcedureContaining(x.owner()) &&
104       x.owner().symbol() && IsFunction(*x.owner().symbol());
105 }
106 
107 static const char *WhyBaseObjectIsSuspicious(
108     const Symbol &x, const Scope &scope) {
109   // See C1594, first paragraph.  These conditions enable checks on both
110   // left-hand and right-hand sides in various circumstances.
111   if (IsHostAssociated(x, scope)) {
112     return "host-associated";
113   } else if (IsUseAssociated(x, scope)) {
114     return "USE-associated";
115   } else if (IsPointerDummyOfPureFunction(x)) {
116     return "a POINTER dummy argument of a pure function";
117   } else if (IsIntentIn(x)) {
118     return "an INTENT(IN) dummy argument";
119   } else if (FindCommonBlockContaining(x)) {
120     return "in a COMMON block";
121   } else {
122     return nullptr;
123   }
124 }
125 
126 // Checks C1594(1,2); false if check fails
127 bool CheckDefinabilityInPureScope(parser::ContextualMessages &messages,
128     const Symbol &lhs, const Scope &context, const Scope &pure) {
129   if (pure.symbol()) {
130     if (const char *why{WhyBaseObjectIsSuspicious(lhs, context)}) {
131       evaluate::SayWithDeclaration(messages, lhs,
132           "Pure subprogram '%s' may not define '%s' because it is %s"_err_en_US,
133           pure.symbol()->name(), lhs.name(), why);
134       return false;
135     }
136   }
137   return true;
138 }
139 
140 static std::optional<std::string> GetPointerComponentDesignatorName(
141     const SomeExpr &expr) {
142   if (const auto *derived{
143           evaluate::GetDerivedTypeSpec(evaluate::DynamicType::From(expr))}) {
144     UltimateComponentIterator ultimates{*derived};
145     if (auto pointer{
146             std::find_if(ultimates.begin(), ultimates.end(), IsPointer)}) {
147       return pointer.BuildResultDesignatorName();
148     }
149   }
150   return std::nullopt;
151 }
152 
153 // Checks C1594(5,6); false if check fails
154 bool CheckCopyabilityInPureScope(parser::ContextualMessages &messages,
155     const SomeExpr &expr, const Scope &scope) {
156   if (const Symbol * base{GetFirstSymbol(expr)}) {
157     if (const char *why{WhyBaseObjectIsSuspicious(*base, scope)}) {
158       if (auto pointer{GetPointerComponentDesignatorName(expr)}) {
159         evaluate::SayWithDeclaration(messages, *base,
160             "A pure subprogram may not copy the value of '%s' because it is %s"
161             " and has the POINTER component '%s'"_err_en_US,
162             base->name(), why, *pointer);
163         return false;
164       }
165     }
166   }
167   return true;
168 }
169 
170 bool AssignmentContext::CheckForPureContext(const SomeExpr &lhs,
171     const SomeExpr &rhs, parser::CharBlock source, bool isPointerAssignment) {
172   const Scope &scope{context_.FindScope(source)};
173   if (const Scope * pure{FindPureProcedureContaining(scope)}) {
174     parser::ContextualMessages messages{
175         context_.location().value(), &context_.messages()};
176     if (evaluate::ExtractCoarrayRef(lhs)) {
177       messages.Say(
178           "A pure subprogram may not define a coindexed object"_err_en_US);
179     } else if (const Symbol * base{GetFirstSymbol(lhs)}) {
180       if (const auto *assoc{base->detailsIf<AssocEntityDetails>()}) {
181         auto dataRef{ExtractDataRef(assoc->expr())};
182         // ASSOCIATE(a=>x) -- check x, not a, for "a=..."
183         base = dataRef ? &dataRef->GetFirstSymbol() : nullptr;
184       }
185       if (base &&
186           !CheckDefinabilityInPureScope(messages, *base, scope, *pure)) {
187         return false;
188       }
189     }
190     if (isPointerAssignment) {
191       if (const Symbol * base{GetFirstSymbol(rhs)}) {
192         if (const char *why{
193                 WhyBaseObjectIsSuspicious(*base, scope)}) {  // C1594(3)
194           evaluate::SayWithDeclaration(messages, *base,
195               "A pure subprogram may not use '%s' as the target of pointer assignment because it is %s"_err_en_US,
196               base->name(), why);
197           return false;
198         }
199       }
200     } else if (auto type{evaluate::DynamicType::From(lhs)}) {
201       // C1596 checks for polymorphic deallocation in a pure subprogram
202       // due to automatic reallocation on assignment
203       if (type->IsPolymorphic()) {
204         context_.Say(
205             "Deallocation of polymorphic object is not permitted in a pure subprogram"_err_en_US);
206         return false;
207       }
208       if (const DerivedTypeSpec * derived{GetDerivedTypeSpec(type)}) {
209         if (auto bad{FindPolymorphicAllocatableNonCoarrayUltimateComponent(
210                 *derived)}) {
211           evaluate::SayWithDeclaration(messages, *bad,
212               "Deallocation of polymorphic non-coarray component '%s' is not permitted in a pure subprogram"_err_en_US,
213               bad.BuildResultDesignatorName());
214           return false;
215         } else {
216           return CheckCopyabilityInPureScope(messages, rhs, scope);
217         }
218       }
219     }
220   }
221   return true;
222 }
223 
224 // 10.2.3.1(2) The masks and LHS of assignments must all have the same shape
225 void AssignmentContext::CheckShape(parser::CharBlock at, const SomeExpr *expr) {
226   if (auto shape{evaluate::GetShape(foldingContext(), expr)}) {
227     std::size_t size{shape->size()};
228     if (whereDepth_ == 0) {
229       whereExtents_.resize(size);
230     } else if (whereExtents_.size() != size) {
231       Say(at,
232           "Must have rank %zd to match prior mask or assignment of"
233           " WHERE construct"_err_en_US,
234           whereExtents_.size());
235       return;
236     }
237     for (std::size_t i{0}; i < size; ++i) {
238       if (std::optional<std::int64_t> extent{evaluate::ToInt64((*shape)[i])}) {
239         if (!whereExtents_[i]) {
240           whereExtents_[i] = *extent;
241         } else if (*whereExtents_[i] != *extent) {
242           Say(at,
243               "Dimension %d must have extent %jd to match prior mask or"
244               " assignment of WHERE construct"_err_en_US,
245               i + 1, static_cast<std::intmax_t>(*whereExtents_[i]));
246         }
247       }
248     }
249   }
250 }
251 
252 template<typename A> void AssignmentContext::PushWhereContext(const A &x) {
253   const auto &expr{std::get<parser::LogicalExpr>(x.t)};
254   CheckShape(expr.thing.value().source, GetExpr(expr));
255   ++whereDepth_;
256 }
257 
258 void AssignmentContext::PopWhereContext() {
259   --whereDepth_;
260   if (whereDepth_ == 0) {
261     whereExtents_.clear();
262   }
263 }
264 
265 AssignmentChecker::~AssignmentChecker() {}
266 
267 AssignmentChecker::AssignmentChecker(SemanticsContext &context)
268   : context_{new AssignmentContext{context}} {}
269 void AssignmentChecker::Enter(const parser::AssignmentStmt &x) {
270   context_.value().Analyze(x);
271 }
272 void AssignmentChecker::Enter(const parser::PointerAssignmentStmt &x) {
273   context_.value().Analyze(x);
274 }
275 void AssignmentChecker::Enter(const parser::WhereStmt &x) {
276   context_.value().PushWhereContext(x);
277 }
278 void AssignmentChecker::Leave(const parser::WhereStmt &) {
279   context_.value().PopWhereContext();
280 }
281 void AssignmentChecker::Enter(const parser::WhereConstructStmt &x) {
282   context_.value().PushWhereContext(x);
283 }
284 void AssignmentChecker::Leave(const parser::EndWhereStmt &) {
285   context_.value().PopWhereContext();
286 }
287 void AssignmentChecker::Enter(const parser::MaskedElsewhereStmt &x) {
288   context_.value().PushWhereContext(x);
289 }
290 void AssignmentChecker::Leave(const parser::MaskedElsewhereStmt &) {
291   context_.value().PopWhereContext();
292 }
293 
294 }
295 template class Fortran::common::Indirection<
296     Fortran::semantics::AssignmentContext>;
297