xref: /llvm-project/flang/lib/Evaluate/check-expression.cpp (revision b670189975f5ba4e8ef22c74724c610287b69c28)
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   bool operator()(const Relational<SomeType> &) const { return false; }
194 
195 private:
196   parser::ContextualMessages *messages_;
197   bool emittedMessage_{false};
198 };
199 
200 bool IsInitialDataTarget(
201     const Expr<SomeType> &x, parser::ContextualMessages *messages) {
202   IsInitialDataTargetHelper helper{messages};
203   bool result{helper(x)};
204   if (!result && messages && !helper.emittedMessage()) {
205     messages->Say(
206         "An initial data target must be a designator with constant subscripts"_err_en_US);
207   }
208   return result;
209 }
210 
211 bool IsInitialProcedureTarget(const semantics::Symbol &symbol) {
212   const auto &ultimate{symbol.GetUltimate()};
213   return std::visit(
214       common::visitors{
215           [](const semantics::SubprogramDetails &) { return true; },
216           [](const semantics::SubprogramNameDetails &) { return true; },
217           [&](const semantics::ProcEntityDetails &proc) {
218             return !semantics::IsPointer(ultimate) && !proc.isDummy();
219           },
220           [](const auto &) { return false; },
221       },
222       ultimate.details());
223 }
224 
225 bool IsInitialProcedureTarget(const ProcedureDesignator &proc) {
226   if (const auto *intrin{proc.GetSpecificIntrinsic()}) {
227     return !intrin->isRestrictedSpecific;
228   } else if (proc.GetComponent()) {
229     return false;
230   } else {
231     return IsInitialProcedureTarget(DEREF(proc.GetSymbol()));
232   }
233 }
234 
235 bool IsInitialProcedureTarget(const Expr<SomeType> &expr) {
236   if (const auto *proc{std::get_if<ProcedureDesignator>(&expr.u)}) {
237     return IsInitialProcedureTarget(*proc);
238   } else {
239     return IsNullPointer(expr);
240   }
241 }
242 
243 // Specification expression validation (10.1.11(2), C1010)
244 class CheckSpecificationExprHelper
245     : public AnyTraverse<CheckSpecificationExprHelper,
246           std::optional<std::string>> {
247 public:
248   using Result = std::optional<std::string>;
249   using Base = AnyTraverse<CheckSpecificationExprHelper, Result>;
250   explicit CheckSpecificationExprHelper(
251       const semantics::Scope &s, const IntrinsicProcTable &table)
252       : Base{*this}, scope_{s}, table_{table} {}
253   using Base::operator();
254 
255   Result operator()(const ProcedureDesignator &) const {
256     return "dummy procedure argument";
257   }
258   Result operator()(const CoarrayRef &) const { return "coindexed reference"; }
259 
260   Result operator()(const semantics::Symbol &symbol) const {
261     const auto &ultimate{symbol.GetUltimate()};
262     if (semantics::IsNamedConstant(ultimate) || ultimate.owner().IsModule() ||
263         ultimate.owner().IsSubmodule()) {
264       return std::nullopt;
265     } else if (scope_.IsDerivedType() &&
266         IsVariableName(ultimate)) { // C750, C754
267       return "derived type component or type parameter value not allowed to "
268              "reference variable '"s +
269           ultimate.name().ToString() + "'";
270     } else if (IsDummy(ultimate)) {
271       if (ultimate.attrs().test(semantics::Attr::OPTIONAL)) {
272         return "reference to OPTIONAL dummy argument '"s +
273             ultimate.name().ToString() + "'";
274       } else if (ultimate.attrs().test(semantics::Attr::INTENT_OUT)) {
275         return "reference to INTENT(OUT) dummy argument '"s +
276             ultimate.name().ToString() + "'";
277       } else if (ultimate.has<semantics::ObjectEntityDetails>()) {
278         return std::nullopt;
279       } else {
280         return "dummy procedure argument";
281       }
282     } else if (const auto *object{
283                    ultimate.detailsIf<semantics::ObjectEntityDetails>()}) {
284       // TODO: what about EQUIVALENCE with data in COMMON?
285       // TODO: does this work for blank COMMON?
286       if (object->commonBlock()) {
287         return std::nullopt;
288       }
289     }
290     for (const semantics::Scope *s{&scope_}; !s->IsGlobal();) {
291       s = &s->parent();
292       if (s == &ultimate.owner()) {
293         return std::nullopt;
294       }
295     }
296     return "reference to local entity '"s + ultimate.name().ToString() + "'";
297   }
298 
299   Result operator()(const Component &x) const {
300     // Don't look at the component symbol.
301     return (*this)(x.base());
302   }
303   Result operator()(const DescriptorInquiry &) const {
304     // Subtle: Uses of SIZE(), LBOUND(), &c. that are valid in specification
305     // expressions will have been converted to expressions over descriptor
306     // inquiries by Fold().
307     return std::nullopt;
308   }
309 
310   Result operator()(const TypeParamInquiry &inq) const {
311     if (scope_.IsDerivedType() && !IsConstantExpr(inq) &&
312         inq.base() /* X%T, not local T */) { // C750, C754
313       return "non-constant reference to a type parameter inquiry not "
314              "allowed for derived type components or type parameter values";
315     }
316     return std::nullopt;
317   }
318 
319   template <typename T> Result operator()(const FunctionRef<T> &x) const {
320     if (const auto *symbol{x.proc().GetSymbol()}) {
321       if (!semantics::IsPureProcedure(*symbol)) {
322         return "reference to impure function '"s + symbol->name().ToString() +
323             "'";
324       }
325       if (semantics::IsStmtFunction(*symbol)) {
326         return "reference to statement function '"s +
327             symbol->name().ToString() + "'";
328       }
329       if (scope_.IsDerivedType()) { // C750, C754
330         return "reference to function '"s + symbol->name().ToString() +
331             "' not allowed for derived type components or type parameter"
332             " values";
333       }
334       // TODO: other checks for standard module procedures
335     } else {
336       const SpecificIntrinsic &intrin{DEREF(x.proc().GetSpecificIntrinsic())};
337       if (scope_.IsDerivedType()) { // C750, C754
338         if ((table_.IsIntrinsic(intrin.name) &&
339                 badIntrinsicsForComponents_.find(intrin.name) !=
340                     badIntrinsicsForComponents_.end()) ||
341             IsProhibitedFunction(intrin.name)) {
342           return "reference to intrinsic '"s + intrin.name +
343               "' not allowed for derived type components or type parameter"
344               " values";
345         }
346         if (table_.GetIntrinsicClass(intrin.name) ==
347                 IntrinsicClass::inquiryFunction &&
348             !IsConstantExpr(x)) {
349           return "non-constant reference to inquiry intrinsic '"s +
350               intrin.name +
351               "' not allowed for derived type components or type"
352               " parameter values";
353         }
354       } else if (intrin.name == "present") {
355         return std::nullopt; // no need to check argument(s)
356       }
357       if (IsConstantExpr(x)) {
358         // inquiry functions may not need to check argument(s)
359         return std::nullopt;
360       }
361     }
362     return (*this)(x.arguments());
363   }
364 
365 private:
366   const semantics::Scope &scope_;
367   const IntrinsicProcTable &table_;
368   const std::set<std::string> badIntrinsicsForComponents_{
369       "allocated", "associated", "extends_type_of", "present", "same_type_as"};
370   static bool IsProhibitedFunction(std::string name) { return false; }
371 };
372 
373 template <typename A>
374 void CheckSpecificationExpr(const A &x, parser::ContextualMessages &messages,
375     const semantics::Scope &scope, const IntrinsicProcTable &table) {
376   if (auto why{CheckSpecificationExprHelper{scope, table}(x)}) {
377     messages.Say("Invalid specification expression: %s"_err_en_US, *why);
378   }
379 }
380 
381 template void CheckSpecificationExpr(const Expr<SomeType> &,
382     parser::ContextualMessages &, const semantics::Scope &,
383     const IntrinsicProcTable &);
384 template void CheckSpecificationExpr(const Expr<SomeInteger> &,
385     parser::ContextualMessages &, const semantics::Scope &,
386     const IntrinsicProcTable &);
387 template void CheckSpecificationExpr(const Expr<SubscriptInteger> &,
388     parser::ContextualMessages &, const semantics::Scope &,
389     const IntrinsicProcTable &);
390 template void CheckSpecificationExpr(const std::optional<Expr<SomeType>> &,
391     parser::ContextualMessages &, const semantics::Scope &,
392     const IntrinsicProcTable &);
393 template void CheckSpecificationExpr(const std::optional<Expr<SomeInteger>> &,
394     parser::ContextualMessages &, const semantics::Scope &,
395     const IntrinsicProcTable &);
396 template void CheckSpecificationExpr(
397     const std::optional<Expr<SubscriptInteger>> &, parser::ContextualMessages &,
398     const semantics::Scope &, const IntrinsicProcTable &);
399 
400 // IsSimplyContiguous() -- 9.5.4
401 class IsSimplyContiguousHelper
402     : public AnyTraverse<IsSimplyContiguousHelper, std::optional<bool>> {
403 public:
404   using Result = std::optional<bool>; // tri-state
405   using Base = AnyTraverse<IsSimplyContiguousHelper, Result>;
406   explicit IsSimplyContiguousHelper(const IntrinsicProcTable &t)
407       : Base{*this}, table_{t} {}
408   using Base::operator();
409 
410   Result operator()(const semantics::Symbol &symbol) const {
411     if (symbol.attrs().test(semantics::Attr::CONTIGUOUS) ||
412         symbol.Rank() == 0) {
413       return true;
414     } else if (semantics::IsPointer(symbol)) {
415       return false;
416     } else if (const auto *details{
417                    symbol.detailsIf<semantics::ObjectEntityDetails>()}) {
418       // N.B. ALLOCATABLEs are deferred shape, not assumed, and
419       // are obviously contiguous.
420       return !details->IsAssumedShape() && !details->IsAssumedRank();
421     } else {
422       return false;
423     }
424   }
425 
426   Result operator()(const ArrayRef &x) const {
427     const auto &symbol{x.GetLastSymbol()};
428     if (!(*this)(symbol)) {
429       return false;
430     } else if (auto rank{CheckSubscripts(x.subscript())}) {
431       // a(:)%b(1,1) is not contiguous; a(1)%b(:,:) is
432       return *rank > 0 || x.Rank() == 0;
433     } else {
434       return false;
435     }
436   }
437   Result operator()(const CoarrayRef &x) const {
438     return CheckSubscripts(x.subscript()).has_value();
439   }
440   Result operator()(const Component &x) const {
441     return x.base().Rank() == 0 && (*this)(x.GetLastSymbol());
442   }
443   Result operator()(const ComplexPart &) const { return false; }
444   Result operator()(const Substring &) const { return false; }
445 
446   template <typename T> Result operator()(const FunctionRef<T> &x) const {
447     if (auto chars{
448             characteristics::Procedure::Characterize(x.proc(), table_)}) {
449       if (chars->functionResult) {
450         const auto &result{*chars->functionResult};
451         return !result.IsProcedurePointer() &&
452             result.attrs.test(characteristics::FunctionResult::Attr::Pointer) &&
453             result.attrs.test(
454                 characteristics::FunctionResult::Attr::Contiguous);
455       }
456     }
457     return false;
458   }
459 
460 private:
461   // If the subscripts can possibly be on a simply-contiguous array reference,
462   // return the rank.
463   static std::optional<int> CheckSubscripts(
464       const std::vector<Subscript> &subscript) {
465     bool anyTriplet{false};
466     int rank{0};
467     for (auto j{subscript.size()}; j-- > 0;) {
468       if (const auto *triplet{std::get_if<Triplet>(&subscript[j].u)}) {
469         if (!triplet->IsStrideOne()) {
470           return std::nullopt;
471         } else if (anyTriplet) {
472           if (triplet->lower() || triplet->upper()) {
473             // all triplets before the last one must be just ":"
474             return std::nullopt;
475           }
476         } else {
477           anyTriplet = true;
478         }
479         ++rank;
480       } else if (anyTriplet || subscript[j].Rank() > 0) {
481         return std::nullopt;
482       }
483     }
484     return rank;
485   }
486 
487   const IntrinsicProcTable &table_;
488 };
489 
490 template <typename A>
491 bool IsSimplyContiguous(const A &x, const IntrinsicProcTable &table) {
492   if (IsVariable(x)) {
493     auto known{IsSimplyContiguousHelper{table}(x)};
494     return known && *known;
495   } else {
496     return true; // not a variable
497   }
498 }
499 
500 template bool IsSimplyContiguous(
501     const Expr<SomeType> &, const IntrinsicProcTable &);
502 
503 } // namespace Fortran::evaluate
504