xref: /llvm-project/flang/lib/Semantics/definable.cpp (revision aad5984b56280d7dc71a43c258c5ed349c9a239f)
1 //===-- lib/Semantics/definable.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 "definable.h"
10 #include "flang/Evaluate/tools.h"
11 #include "flang/Semantics/tools.h"
12 
13 using namespace Fortran::parser::literals;
14 
15 namespace Fortran::semantics {
16 
17 template <typename... A>
18 static parser::Message BlameSymbol(parser::CharBlock at,
19     const parser::MessageFixedText &text, const Symbol &original, A &&...x) {
20   parser::Message message{at, text, original.name(), std::forward<A>(x)...};
21   message.set_severity(parser::Severity::Because);
22   evaluate::AttachDeclaration(message, original);
23   return message;
24 }
25 
26 static bool IsPointerDummyOfPureFunction(const Symbol &x) {
27   return IsPointerDummy(x) && FindPureProcedureContaining(x.owner()) &&
28       x.owner().symbol() && IsFunction(*x.owner().symbol());
29 }
30 
31 // See C1594, first paragraph.  These conditions enable checks on both
32 // left-hand and right-hand sides in various circumstances.
33 const char *WhyBaseObjectIsSuspicious(const Symbol &x, const Scope &scope) {
34   if (IsHostAssociatedIntoSubprogram(x, scope)) {
35     return "host-associated";
36   } else if (IsUseAssociated(x, scope)) {
37     return "USE-associated";
38   } else if (IsPointerDummyOfPureFunction(x)) {
39     return "a POINTER dummy argument of a pure function";
40   } else if (IsIntentIn(x)) {
41     return "an INTENT(IN) dummy argument";
42   } else if (FindCommonBlockContaining(x)) {
43     return "in a COMMON block";
44   } else {
45     return nullptr;
46   }
47 }
48 
49 // Checks C1594(1,2); false if check fails
50 static std::optional<parser::Message> CheckDefinabilityInPureScope(
51     SourceName at, const Symbol &original, const Symbol &ultimate,
52     const Scope &context, const Scope &pure) {
53   if (pure.symbol()) {
54     if (const char *why{WhyBaseObjectIsSuspicious(ultimate, context)}) {
55       return BlameSymbol(at,
56           "'%s' may not be defined in pure subprogram '%s' because it is %s"_en_US,
57           original, pure.symbol()->name(), why);
58     }
59   }
60   return std::nullopt;
61 }
62 
63 // When a DataRef contains pointers, gets the rightmost one (unless it is
64 // the entity being defined, in which case the last pointer above it);
65 // otherwise, returns the leftmost symbol.  The resulting symbol is the
66 // relevant base object for definabiliy checking.  Examples:
67 //   ptr1%ptr2        => ...     -> ptr1
68 //   nonptr%ptr       => ...     -> nonptr
69 //   nonptr%ptr       =  ...     -> ptr
70 //   ptr1%ptr2        =  ...     -> ptr2
71 //   ptr1%ptr2%nonptr =  ...     -> ptr2
72 //   nonptr1%nonptr2  =  ...     -> nonptr1
73 static const Symbol &GetRelevantSymbol(const evaluate::DataRef &dataRef,
74     bool isPointerDefinition, bool acceptAllocatable) {
75   if (isPointerDefinition) {
76     if (const auto *component{std::get_if<evaluate::Component>(&dataRef.u)}) {
77       if (IsPointer(component->GetLastSymbol()) ||
78           (acceptAllocatable && IsAllocatable(component->GetLastSymbol()))) {
79         return GetRelevantSymbol(component->base(), false, false);
80       }
81     }
82   }
83   if (const Symbol * lastPointer{GetLastPointerSymbol(dataRef)}) {
84     return *lastPointer;
85   } else {
86     return dataRef.GetFirstSymbol();
87   }
88 }
89 
90 // Check the leftmost (or only) symbol from a data-ref or expression.
91 static std::optional<parser::Message> WhyNotDefinableBase(parser::CharBlock at,
92     const Scope &scope, DefinabilityFlags flags, const Symbol &original) {
93   const Symbol &ultimate{original.GetUltimate()};
94   bool isPointerDefinition{flags.test(DefinabilityFlag::PointerDefinition)};
95   bool acceptAllocatable{flags.test(DefinabilityFlag::AcceptAllocatable)};
96   bool isTargetDefinition{!isPointerDefinition && IsPointer(ultimate)};
97   if (const auto *association{ultimate.detailsIf<AssocEntityDetails>()}) {
98     if (association->rank().has_value()) {
99       return std::nullopt; // SELECT RANK always modifiable variable
100     } else if (!IsVariable(association->expr())) {
101       return BlameSymbol(at,
102           "'%s' is construct associated with an expression"_en_US, original);
103     } else if (evaluate::HasVectorSubscript(association->expr().value())) {
104       return BlameSymbol(at,
105           "Construct association '%s' has a vector subscript"_en_US, original);
106     } else if (auto dataRef{evaluate::ExtractDataRef(
107                    *association->expr(), true, true)}) {
108       return WhyNotDefinableBase(at, scope, flags,
109           GetRelevantSymbol(*dataRef, isPointerDefinition, acceptAllocatable));
110     }
111   }
112   if (isTargetDefinition) {
113   } else if (!isPointerDefinition && !IsVariableName(ultimate)) {
114     return BlameSymbol(at, "'%s' is not a variable"_en_US, original);
115   } else if (IsProtected(ultimate) && IsUseAssociated(original, scope)) {
116     return BlameSymbol(at, "'%s' is protected in this scope"_en_US, original);
117   } else if (IsIntentIn(ultimate)) {
118     return BlameSymbol(
119         at, "'%s' is an INTENT(IN) dummy argument"_en_US, original);
120   }
121   if (const Scope * pure{FindPureProcedureContaining(scope)}) {
122     // Additional checking for pure subprograms.
123     if (!isTargetDefinition) {
124       if (auto msg{CheckDefinabilityInPureScope(
125               at, original, ultimate, scope, *pure)}) {
126         return msg;
127       }
128     }
129     if (const Symbol *
130         visible{FindExternallyVisibleObject(
131             ultimate, *pure, isPointerDefinition)}) {
132       return BlameSymbol(at,
133           "'%s' is externally visible via '%s' and not definable in a pure subprogram"_en_US,
134           original, visible->name());
135     }
136   }
137   return std::nullopt;
138 }
139 
140 static std::optional<parser::Message> WhyNotDefinableLast(parser::CharBlock at,
141     const Scope &scope, DefinabilityFlags flags, const Symbol &original) {
142   const Symbol &ultimate{original.GetUltimate()};
143   if (flags.test(DefinabilityFlag::PointerDefinition)) {
144     if (flags.test(DefinabilityFlag::AcceptAllocatable)) {
145       if (!IsAllocatableOrPointer(ultimate)) {
146         return BlameSymbol(
147             at, "'%s' is neither a pointer nor an allocatable"_en_US, original);
148       }
149     } else if (!IsPointer(ultimate)) {
150       return BlameSymbol(at, "'%s' is not a pointer"_en_US, original);
151     }
152     return std::nullopt; // pointer assignment - skip following checks
153   }
154   if (IsOrContainsEventOrLockComponent(ultimate)) {
155     return BlameSymbol(at,
156         "'%s' is an entity with either an EVENT_TYPE or LOCK_TYPE"_en_US,
157         original);
158   }
159   if (!flags.test(DefinabilityFlag::PolymorphicOkInPure) &&
160       FindPureProcedureContaining(scope)) {
161     if (auto dyType{evaluate::DynamicType::From(ultimate)}) {
162       if (dyType->IsPolymorphic()) { // C1596
163         return BlameSymbol(at,
164             "'%s' is polymorphic in a pure subprogram"_because_en_US, original);
165       }
166       if (const DerivedTypeSpec * derived{GetDerivedTypeSpec(dyType)}) {
167         if (auto bad{FindPolymorphicAllocatableNonCoarrayUltimateComponent(
168                 *derived)}) {
169           return BlameSymbol(at,
170               "'%s' has polymorphic non-coarray component '%s' in a pure subprogram"_because_en_US,
171               original, bad.BuildResultDesignatorName());
172         }
173       }
174     }
175   }
176   return std::nullopt;
177 }
178 
179 // Checks a data-ref
180 static std::optional<parser::Message> WhyNotDefinable(parser::CharBlock at,
181     const Scope &scope, DefinabilityFlags flags,
182     const evaluate::DataRef &dataRef) {
183   const Symbol &base{GetRelevantSymbol(dataRef,
184       flags.test(DefinabilityFlag::PointerDefinition),
185       flags.test(DefinabilityFlag::AcceptAllocatable))};
186   if (auto whyNot{WhyNotDefinableBase(at, scope, flags, base)}) {
187     return whyNot;
188   } else {
189     return WhyNotDefinableLast(at, scope, flags, dataRef.GetLastSymbol());
190   }
191 }
192 
193 // Checks a NOPASS procedure pointer component
194 static std::optional<parser::Message> WhyNotDefinable(parser::CharBlock at,
195     const Scope &scope, DefinabilityFlags flags,
196     const evaluate::Component &component) {
197   const evaluate::DataRef &dataRef{component.base()};
198   const Symbol &base{GetRelevantSymbol(dataRef, false, false)};
199   DefinabilityFlags baseFlags{flags};
200   baseFlags.reset(DefinabilityFlag::PointerDefinition);
201   return WhyNotDefinableBase(at, scope, baseFlags, base);
202 }
203 
204 std::optional<parser::Message> WhyNotDefinable(parser::CharBlock at,
205     const Scope &scope, DefinabilityFlags flags, const Symbol &original) {
206   if (auto base{WhyNotDefinableBase(at, scope, flags, original)}) {
207     return base;
208   }
209   return WhyNotDefinableLast(at, scope, flags, original);
210 }
211 
212 std::optional<parser::Message> WhyNotDefinable(parser::CharBlock at,
213     const Scope &scope, DefinabilityFlags flags,
214     const evaluate::Expr<evaluate::SomeType> &expr) {
215   if (auto dataRef{evaluate::ExtractDataRef(expr, true, true)}) {
216     if (evaluate::HasVectorSubscript(expr)) {
217       if (flags.test(DefinabilityFlag::VectorSubscriptIsOk)) {
218         if (auto type{expr.GetType()}) {
219           if (!type->IsUnlimitedPolymorphic() &&
220               type->category() == TypeCategory::Derived) {
221             // Seek the FINAL subroutine that should but cannot be called
222             // for this definition of an array with a vector-valued subscript.
223             // If there's an elemental FINAL subroutine, all is well; otherwise,
224             // if there is a FINAL subroutine with a matching or assumed rank
225             // dummy argument, there's no way to call it.
226             int rank{expr.Rank()};
227             const DerivedTypeSpec *spec{&type->GetDerivedTypeSpec()};
228             while (spec) {
229               bool anyElemental{false};
230               const Symbol *anyRankMatch{nullptr};
231               for (const auto &[_, ref] :
232                   spec->typeSymbol().get<DerivedTypeDetails>().finals()) {
233                 const Symbol &ultimate{ref->GetUltimate()};
234                 anyElemental |= ultimate.attrs().test(Attr::ELEMENTAL);
235                 if (const auto *subp{ultimate.detailsIf<SubprogramDetails>()}) {
236                   if (!subp->dummyArgs().empty()) {
237                     if (const Symbol * arg{subp->dummyArgs()[0]}) {
238                       const auto *object{arg->detailsIf<ObjectEntityDetails>()};
239                       if (arg->Rank() == rank ||
240                           (object && object->IsAssumedRank())) {
241                         anyRankMatch = &*ref;
242                       }
243                     }
244                   }
245                 }
246               }
247               if (anyRankMatch && !anyElemental) {
248                 return parser::Message{at,
249                     "Variable '%s' has a vector subscript and cannot be finalized by non-elemental subroutine '%s'"_because_en_US,
250                     expr.AsFortran(), anyRankMatch->name()};
251               }
252               const auto *parent{FindParentTypeSpec(*spec)};
253               spec = parent ? parent->AsDerived() : nullptr;
254             }
255           }
256         }
257       } else {
258         return parser::Message{at,
259             "Variable '%s' has a vector subscript"_because_en_US,
260             expr.AsFortran()};
261       }
262     }
263     if (FindPureProcedureContaining(scope) &&
264         evaluate::ExtractCoarrayRef(expr)) {
265       return parser::Message(at,
266           "A pure subprogram may not define the coindexed object '%s'"_because_en_US,
267           expr.AsFortran());
268     }
269     return WhyNotDefinable(at, scope, flags, *dataRef);
270   }
271   if (evaluate::IsVariable(expr)) {
272     return std::nullopt; // result of function returning a pointer - ok
273   }
274   if (flags.test(DefinabilityFlag::PointerDefinition)) {
275     if (const auto *procDesignator{
276             std::get_if<evaluate::ProcedureDesignator>(&expr.u)}) {
277       // Defining a procedure pointer
278       if (const Symbol * procSym{procDesignator->GetSymbol()}) {
279         if (evaluate::ExtractCoarrayRef(expr)) { // C1027
280           return BlameSymbol(at,
281               "Procedure pointer '%s' may not be a coindexed object"_because_en_US,
282               *procSym, expr.AsFortran());
283         }
284         if (const auto *component{procDesignator->GetComponent()}) {
285           return WhyNotDefinable(at, scope, flags, *component);
286         } else {
287           return WhyNotDefinable(at, scope, flags, *procSym);
288         }
289       }
290     }
291   }
292   if (evaluate::IsNullPointer(expr)) {
293     return parser::Message{
294         at, "'%s' is a null pointer"_because_en_US, expr.AsFortran()};
295   }
296   return parser::Message{
297       at, "'%s' is not a variable or pointer"_because_en_US, expr.AsFortran()};
298 }
299 
300 } // namespace Fortran::semantics
301