xref: /llvm-project/flang/lib/Evaluate/check-expression.cpp (revision 5349f99114cfcf597220cbc9b9253f178a43fabd)
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/intrinsics.h"
11 #include "flang/Evaluate/traverse.h"
12 #include "flang/Evaluate/type.h"
13 #include "flang/Semantics/symbol.h"
14 #include "flang/Semantics/tools.h"
15 #include <set>
16 #include <string>
17 
18 namespace Fortran::evaluate {
19 
20 // Constant expression predicate IsConstantExpr().
21 // This code determines whether an expression is a "constant expression"
22 // in the sense of section 10.1.12.  This is not the same thing as being
23 // able to fold it (yet) into a known constant value; specifically,
24 // the expression may reference derived type kind parameters whose values
25 // are not yet known.
26 class IsConstantExprHelper : public AllTraverse<IsConstantExprHelper, true> {
27 public:
28   using Base = AllTraverse<IsConstantExprHelper, true>;
29   IsConstantExprHelper() : Base{*this} {}
30   using Base::operator();
31 
32   bool operator()(const TypeParamInquiry &inq) const {
33     return IsKindTypeParameter(inq.parameter());
34   }
35   bool operator()(const semantics::Symbol &symbol) const {
36     const auto &ultimate{symbol.GetUltimate()};
37     return IsNamedConstant(ultimate) || IsImpliedDoIndex(ultimate) ||
38         IsInitialProcedureTarget(ultimate);
39   }
40   bool operator()(const CoarrayRef &) const { return false; }
41   bool operator()(const semantics::ParamValue &param) const {
42     return param.isExplicit() && (*this)(param.GetExplicit());
43   }
44   template <typename T> bool operator()(const FunctionRef<T> &call) const {
45     if (const auto *intrinsic{std::get_if<SpecificIntrinsic>(&call.proc().u)}) {
46       // kind is always a constant, and we avoid cascading errors by calling
47       // invalid calls to intrinsics constant
48       return intrinsic->name == "kind" ||
49           intrinsic->name == IntrinsicProcTable::InvalidName;
50       // TODO: other inquiry intrinsics
51     } else {
52       return false;
53     }
54   }
55   bool operator()(const StructureConstructor &constructor) const {
56     for (const auto &[symRef, expr] : constructor) {
57       if (!IsConstantStructureConstructorComponent(*symRef, expr.value())) {
58         return false;
59       }
60     }
61     return true;
62   }
63   bool operator()(const Component &component) const {
64     return (*this)(component.base());
65   }
66   // Forbid integer division by zero in constants.
67   template <int KIND>
68   bool operator()(
69       const Divide<Type<TypeCategory::Integer, KIND>> &division) const {
70     using T = Type<TypeCategory::Integer, KIND>;
71     if (const auto divisor{GetScalarConstantValue<T>(division.right())}) {
72       return !divisor->IsZero() && (*this)(division.left());
73     } else {
74       return false;
75     }
76   }
77 
78   bool operator()(const Constant<SomeDerived> &) const { return true; }
79 
80 private:
81   bool IsConstantStructureConstructorComponent(
82       const Symbol &component, const Expr<SomeType> &expr) const {
83     if (IsAllocatable(component)) {
84       return IsNullPointer(expr);
85     } else if (IsPointer(component)) {
86       return IsNullPointer(expr) || IsInitialDataTarget(expr) ||
87           IsInitialProcedureTarget(expr);
88     } else {
89       return (*this)(expr);
90     }
91   }
92 };
93 
94 template <typename A> bool IsConstantExpr(const A &x) {
95   return IsConstantExprHelper{}(x);
96 }
97 template bool IsConstantExpr(const Expr<SomeType> &);
98 template bool IsConstantExpr(const Expr<SomeInteger> &);
99 template bool IsConstantExpr(const Expr<SubscriptInteger> &);
100 template bool IsConstantExpr(const StructureConstructor &);
101 
102 // Object pointer initialization checking predicate IsInitialDataTarget().
103 // This code determines whether an expression is allowable as the static
104 // data address used to initialize a pointer with "=> x".  See C765.
105 class IsInitialDataTargetHelper
106     : public AllTraverse<IsInitialDataTargetHelper, true> {
107 public:
108   using Base = AllTraverse<IsInitialDataTargetHelper, true>;
109   using Base::operator();
110   explicit IsInitialDataTargetHelper(parser::ContextualMessages *m)
111       : Base{*this}, messages_{m} {}
112 
113   bool emittedMessage() const { return emittedMessage_; }
114 
115   bool operator()(const BOZLiteralConstant &) const { return false; }
116   bool operator()(const NullPointer &) const { return true; }
117   template <typename T> bool operator()(const Constant<T> &) const {
118     return false;
119   }
120   bool operator()(const semantics::Symbol &symbol) {
121     const Symbol &ultimate{symbol.GetUltimate()};
122     if (IsAllocatable(ultimate)) {
123       if (messages_) {
124         messages_->Say(
125             "An initial data target may not be a reference to an ALLOCATABLE '%s'"_err_en_US,
126             ultimate.name());
127         emittedMessage_ = true;
128       }
129       return false;
130     } else if (ultimate.Corank() > 0) {
131       if (messages_) {
132         messages_->Say(
133             "An initial data target may not be a reference to a coarray '%s'"_err_en_US,
134             ultimate.name());
135         emittedMessage_ = true;
136       }
137       return false;
138     } else if (!ultimate.attrs().test(semantics::Attr::TARGET)) {
139       if (messages_) {
140         messages_->Say(
141             "An initial data target may not be a reference to an object '%s' that lacks the TARGET attribute"_err_en_US,
142             ultimate.name());
143         emittedMessage_ = true;
144       }
145       return false;
146     } else if (!IsSaved(ultimate)) {
147       if (messages_) {
148         messages_->Say(
149             "An initial data target may not be a reference to an object '%s' that lacks the SAVE attribute"_err_en_US,
150             ultimate.name());
151         emittedMessage_ = true;
152       }
153       return false;
154     }
155     return true;
156   }
157   bool operator()(const StaticDataObject &) const { return false; }
158   bool operator()(const TypeParamInquiry &) const { return false; }
159   bool operator()(const Triplet &x) const {
160     return IsConstantExpr(x.lower()) && IsConstantExpr(x.upper()) &&
161         IsConstantExpr(x.stride());
162   }
163   bool operator()(const Subscript &x) const {
164     return std::visit(common::visitors{
165                           [&](const Triplet &t) { return (*this)(t); },
166                           [&](const auto &y) {
167                             return y.value().Rank() == 0 &&
168                                 IsConstantExpr(y.value());
169                           },
170                       },
171         x.u);
172   }
173   bool operator()(const CoarrayRef &) const { return false; }
174   bool operator()(const Substring &x) const {
175     return IsConstantExpr(x.lower()) && IsConstantExpr(x.upper()) &&
176         (*this)(x.parent());
177   }
178   bool operator()(const DescriptorInquiry &) const { return false; }
179   template <typename T> bool operator()(const ArrayConstructor<T> &) const {
180     return false;
181   }
182   bool operator()(const StructureConstructor &) const { return false; }
183   template <typename T> bool operator()(const FunctionRef<T> &) {
184     return false;
185   }
186   template <typename D, typename R, typename... O>
187   bool operator()(const Operation<D, R, O...> &) const {
188     return false;
189   }
190   template <typename T> bool operator()(const Parentheses<T> &x) const {
191     return (*this)(x.left());
192   }
193   template <typename T> bool operator()(const FunctionRef<T> &x) const {
194     return false;
195   }
196   bool operator()(const Relational<SomeType> &) const { return false; }
197 
198 private:
199   parser::ContextualMessages *messages_;
200   bool emittedMessage_{false};
201 };
202 
203 bool IsInitialDataTarget(
204     const Expr<SomeType> &x, parser::ContextualMessages *messages) {
205   IsInitialDataTargetHelper helper{messages};
206   bool result{helper(x)};
207   if (!result && messages && !helper.emittedMessage()) {
208     messages->Say(
209         "An initial data target must be a designator with constant subscripts"_err_en_US);
210   }
211   return result;
212 }
213 
214 bool IsInitialProcedureTarget(const semantics::Symbol &symbol) {
215   const auto &ultimate{symbol.GetUltimate()};
216   return std::visit(
217       common::visitors{
218           [](const semantics::SubprogramDetails &) { return true; },
219           [](const semantics::SubprogramNameDetails &) { return true; },
220           [&](const semantics::ProcEntityDetails &proc) {
221             return !semantics::IsPointer(ultimate) && !proc.isDummy();
222           },
223           [](const auto &) { return false; },
224       },
225       ultimate.details());
226 }
227 
228 bool IsInitialProcedureTarget(const ProcedureDesignator &proc) {
229   if (const auto *intrin{proc.GetSpecificIntrinsic()}) {
230     return !intrin->isRestrictedSpecific;
231   } else if (proc.GetComponent()) {
232     return false;
233   } else {
234     return IsInitialProcedureTarget(DEREF(proc.GetSymbol()));
235   }
236 }
237 
238 bool IsInitialProcedureTarget(const Expr<SomeType> &expr) {
239   if (const auto *proc{std::get_if<ProcedureDesignator>(&expr.u)}) {
240     return IsInitialProcedureTarget(*proc);
241   } else {
242     return IsNullPointer(expr);
243   }
244 }
245 
246 // Specification expression validation (10.1.11(2), C1010)
247 class CheckSpecificationExprHelper
248     : public AnyTraverse<CheckSpecificationExprHelper,
249           std::optional<std::string>> {
250 public:
251   using Result = std::optional<std::string>;
252   using Base = AnyTraverse<CheckSpecificationExprHelper, Result>;
253   explicit CheckSpecificationExprHelper(
254       const semantics::Scope &s, const IntrinsicProcTable &table)
255       : Base{*this}, scope_{s}, table_{table} {}
256   using Base::operator();
257 
258   Result operator()(const ProcedureDesignator &) const {
259     return "dummy procedure argument";
260   }
261   Result operator()(const CoarrayRef &) const { return "coindexed reference"; }
262 
263   Result operator()(const semantics::Symbol &symbol) const {
264     const auto &ultimate{symbol.GetUltimate()};
265     if (semantics::IsNamedConstant(ultimate) || ultimate.owner().IsModule() ||
266         ultimate.owner().IsSubmodule()) {
267       return std::nullopt;
268     } else if (scope_.IsDerivedType() &&
269         IsVariableName(ultimate)) { // C750, C754
270       return "derived type component or type parameter value not allowed to "
271              "reference variable '"s +
272           ultimate.name().ToString() + "'";
273     } else if (IsDummy(ultimate)) {
274       if (ultimate.attrs().test(semantics::Attr::OPTIONAL)) {
275         return "reference to OPTIONAL dummy argument '"s +
276             ultimate.name().ToString() + "'";
277       } else if (ultimate.attrs().test(semantics::Attr::INTENT_OUT)) {
278         return "reference to INTENT(OUT) dummy argument '"s +
279             ultimate.name().ToString() + "'";
280       } else if (ultimate.has<semantics::ObjectEntityDetails>()) {
281         return std::nullopt;
282       } else {
283         return "dummy procedure argument";
284       }
285     } else if (const auto *object{
286                    ultimate.detailsIf<semantics::ObjectEntityDetails>()}) {
287       // TODO: what about EQUIVALENCE with data in COMMON?
288       // TODO: does this work for blank COMMON?
289       if (object->commonBlock()) {
290         return std::nullopt;
291       }
292     }
293     for (const semantics::Scope *s{&scope_}; !s->IsGlobal();) {
294       s = &s->parent();
295       if (s == &ultimate.owner()) {
296         return std::nullopt;
297       }
298     }
299     return "reference to local entity '"s + ultimate.name().ToString() + "'";
300   }
301 
302   Result operator()(const Component &x) const {
303     // Don't look at the component symbol.
304     return (*this)(x.base());
305   }
306   Result operator()(const DescriptorInquiry &) const {
307     // Subtle: Uses of SIZE(), LBOUND(), &c. that are valid in specification
308     // expressions will have been converted to expressions over descriptor
309     // inquiries by Fold().
310     return std::nullopt;
311   }
312 
313   Result operator()(const TypeParamInquiry &inq) const {
314     if (scope_.IsDerivedType() && !IsConstantExpr(inq) &&
315         inq.base() /* X%T, not local T */) { // C750, C754
316       return "non-constant reference to a type parameter inquiry not "
317              "allowed for derived type components or type parameter values";
318     }
319     return std::nullopt;
320   }
321 
322   template <typename T> Result operator()(const FunctionRef<T> &x) const {
323     if (const auto *symbol{x.proc().GetSymbol()}) {
324       if (!semantics::IsPureProcedure(*symbol)) {
325         return "reference to impure function '"s + symbol->name().ToString() +
326             "'";
327       }
328       if (semantics::IsStmtFunction(*symbol)) {
329         return "reference to statement function '"s +
330             symbol->name().ToString() + "'";
331       }
332       if (scope_.IsDerivedType()) { // C750, C754
333         return "reference to function '"s + symbol->name().ToString() +
334             "' not allowed for derived type components or type parameter"
335             " values";
336       }
337       // TODO: other checks for standard module procedures
338     } else {
339       const SpecificIntrinsic &intrin{DEREF(x.proc().GetSpecificIntrinsic())};
340       if (scope_.IsDerivedType()) { // C750, C754
341         if ((table_.IsIntrinsic(intrin.name) &&
342                 badIntrinsicsForComponents_.find(intrin.name) !=
343                     badIntrinsicsForComponents_.end()) ||
344             IsProhibitedFunction(intrin.name)) {
345           return "reference to intrinsic '"s + intrin.name +
346               "' not allowed for derived type components or type parameter"
347               " values";
348         }
349         if (table_.GetIntrinsicClass(intrin.name) ==
350                 IntrinsicClass::inquiryFunction &&
351             !IsConstantExpr(x)) {
352           return "non-constant reference to inquiry intrinsic '"s +
353               intrin.name +
354               "' not allowed for derived type components or type"
355               " parameter values";
356         }
357       } else if (intrin.name == "present") {
358         return std::nullopt; // no need to check argument(s)
359       }
360       if (IsConstantExpr(x)) {
361         // inquiry functions may not need to check argument(s)
362         return std::nullopt;
363       }
364     }
365     return (*this)(x.arguments());
366   }
367 
368 private:
369   const semantics::Scope &scope_;
370   const IntrinsicProcTable &table_;
371   const std::set<std::string> badIntrinsicsForComponents_{
372       "allocated", "associated", "extends_type_of", "present", "same_type_as"};
373   static bool IsProhibitedFunction(std::string name) { return false; }
374 };
375 
376 template <typename A>
377 void CheckSpecificationExpr(const A &x, parser::ContextualMessages &messages,
378     const semantics::Scope &scope, const IntrinsicProcTable &table) {
379   if (auto why{CheckSpecificationExprHelper{scope, table}(x)}) {
380     messages.Say("Invalid specification expression: %s"_err_en_US, *why);
381   }
382 }
383 
384 template void CheckSpecificationExpr(const Expr<SomeType> &,
385     parser::ContextualMessages &, const semantics::Scope &,
386     const IntrinsicProcTable &);
387 template void CheckSpecificationExpr(const Expr<SomeInteger> &,
388     parser::ContextualMessages &, const semantics::Scope &,
389     const IntrinsicProcTable &);
390 template void CheckSpecificationExpr(const Expr<SubscriptInteger> &,
391     parser::ContextualMessages &, const semantics::Scope &,
392     const IntrinsicProcTable &);
393 template void CheckSpecificationExpr(const std::optional<Expr<SomeType>> &,
394     parser::ContextualMessages &, const semantics::Scope &,
395     const IntrinsicProcTable &);
396 template void CheckSpecificationExpr(const std::optional<Expr<SomeInteger>> &,
397     parser::ContextualMessages &, const semantics::Scope &,
398     const IntrinsicProcTable &);
399 template void CheckSpecificationExpr(
400     const std::optional<Expr<SubscriptInteger>> &, parser::ContextualMessages &,
401     const semantics::Scope &, const IntrinsicProcTable &);
402 
403 // IsSimplyContiguous() -- 9.5.4
404 class IsSimplyContiguousHelper
405     : public AnyTraverse<IsSimplyContiguousHelper, std::optional<bool>> {
406 public:
407   using Result = std::optional<bool>; // tri-state
408   using Base = AnyTraverse<IsSimplyContiguousHelper, Result>;
409   explicit IsSimplyContiguousHelper(const IntrinsicProcTable &t)
410       : Base{*this}, table_{t} {}
411   using Base::operator();
412 
413   Result operator()(const semantics::Symbol &symbol) const {
414     if (symbol.attrs().test(semantics::Attr::CONTIGUOUS) ||
415         symbol.Rank() == 0) {
416       return true;
417     } else if (semantics::IsPointer(symbol)) {
418       return false;
419     } else if (const auto *details{
420                    symbol.detailsIf<semantics::ObjectEntityDetails>()}) {
421       // N.B. ALLOCATABLEs are deferred shape, not assumed, and
422       // are obviously contiguous.
423       return !details->IsAssumedShape() && !details->IsAssumedRank();
424     } else {
425       return false;
426     }
427   }
428 
429   Result operator()(const ArrayRef &x) const {
430     const auto &symbol{x.GetLastSymbol()};
431     if (!(*this)(symbol)) {
432       return false;
433     } else if (auto rank{CheckSubscripts(x.subscript())}) {
434       // a(:)%b(1,1) is not contiguous; a(1)%b(:,:) is
435       return *rank > 0 || x.Rank() == 0;
436     } else {
437       return false;
438     }
439   }
440   Result operator()(const CoarrayRef &x) const {
441     return CheckSubscripts(x.subscript()).has_value();
442   }
443   Result operator()(const Component &x) const {
444     return x.base().Rank() == 0 && (*this)(x.GetLastSymbol());
445   }
446   Result operator()(const ComplexPart &) const { return false; }
447   Result operator()(const Substring &) const { return false; }
448 
449   template <typename T> Result operator()(const FunctionRef<T> &x) const {
450     if (auto chars{
451             characteristics::Procedure::Characterize(x.proc(), table_)}) {
452       if (chars->functionResult) {
453         const auto &result{*chars->functionResult};
454         return !result.IsProcedurePointer() &&
455             result.attrs.test(characteristics::FunctionResult::Attr::Pointer) &&
456             result.attrs.test(
457                 characteristics::FunctionResult::Attr::Contiguous);
458       }
459     }
460     return false;
461   }
462 
463 private:
464   // If the subscripts can possibly be on a simply-contiguous array reference,
465   // return the rank.
466   static std::optional<int> CheckSubscripts(
467       const std::vector<Subscript> &subscript) {
468     bool anyTriplet{false};
469     int rank{0};
470     for (auto j{subscript.size()}; j-- > 0;) {
471       if (const auto *triplet{std::get_if<Triplet>(&subscript[j].u)}) {
472         if (!triplet->IsStrideOne()) {
473           return std::nullopt;
474         } else if (anyTriplet) {
475           if (triplet->lower() || triplet->upper()) {
476             // all triplets before the last one must be just ":"
477             return std::nullopt;
478           }
479         } else {
480           anyTriplet = true;
481         }
482         ++rank;
483       } else if (anyTriplet || subscript[j].Rank() > 0) {
484         return std::nullopt;
485       }
486     }
487     return rank;
488   }
489 
490   const IntrinsicProcTable &table_;
491 };
492 
493 template <typename A>
494 bool IsSimplyContiguous(const A &x, const IntrinsicProcTable &table) {
495   if (IsVariable(x)) {
496     auto known{IsSimplyContiguousHelper{table}(x)};
497     return known && *known;
498   } else {
499     return true; // not a variable
500   }
501 }
502 
503 template bool IsSimplyContiguous(
504     const Expr<SomeType> &, const IntrinsicProcTable &);
505 
506 } // namespace Fortran::evaluate
507