xref: /llvm-project/flang/lib/Semantics/definable.cpp (revision 07b3bba901e7d51b3173631d6af811eae9d84cda)
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::Error);
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 // True when the object being defined is not a subobject of the base
64 // object, e.g. X%PTR = 1., X%PTR%PTR2 => T (but not X%PTR => T).
65 // F'2023 9.4.2p5
66 static bool DefinesComponentPointerTarget(
67     const evaluate::DataRef &dataRef, DefinabilityFlags flags) {
68   if (const evaluate::Component *
69       component{common::visit(
70           common::visitors{
71               [](const SymbolRef &) -> const evaluate::Component * {
72                 return nullptr;
73               },
74               [](const evaluate::Component &component) { return &component; },
75               [](const evaluate::ArrayRef &aRef) {
76                 return aRef.base().UnwrapComponent();
77               },
78               [](const evaluate::CoarrayRef &aRef)
79                   -> const evaluate::Component * { return nullptr; },
80           },
81           dataRef.u)}) {
82     const Symbol &compSym{component->GetLastSymbol()};
83     if (IsPointer(compSym) ||
84         (flags.test(DefinabilityFlag::AcceptAllocatable) &&
85             IsAllocatable(compSym))) {
86       if (!flags.test(DefinabilityFlag::PointerDefinition)) {
87         return true;
88       }
89     }
90     flags.reset(DefinabilityFlag::PointerDefinition);
91     return DefinesComponentPointerTarget(component->base(), flags);
92   } else {
93     return false;
94   }
95 }
96 
97 // Check the leftmost (or only) symbol from a data-ref or expression.
98 static std::optional<parser::Message> WhyNotDefinableBase(parser::CharBlock at,
99     const Scope &scope, DefinabilityFlags flags, const Symbol &original,
100     bool isWholeSymbol, bool isComponentPointerTarget) {
101   const Symbol &ultimate{original.GetUltimate()};
102   bool isPointerDefinition{flags.test(DefinabilityFlag::PointerDefinition)};
103   bool acceptAllocatable{flags.test(DefinabilityFlag::AcceptAllocatable)};
104   bool isTargetDefinition{!isPointerDefinition && IsPointer(ultimate)};
105   if (const auto *association{ultimate.detailsIf<AssocEntityDetails>()}) {
106     if (!IsVariable(association->expr())) {
107       return BlameSymbol(at,
108           "'%s' is construct associated with an expression"_en_US, original);
109     } else if (evaluate::HasVectorSubscript(association->expr().value())) {
110       return BlameSymbol(at,
111           "Construct association '%s' has a vector subscript"_en_US, original);
112     } else if (auto dataRef{evaluate::ExtractDataRef(
113                    *association->expr(), true, true)}) {
114       return WhyNotDefinableBase(at, scope, flags, dataRef->GetFirstSymbol(),
115           isWholeSymbol &&
116               std::holds_alternative<evaluate::SymbolRef>(dataRef->u),
117           isComponentPointerTarget ||
118               DefinesComponentPointerTarget(*dataRef, flags));
119     }
120   }
121   if (isTargetDefinition || isComponentPointerTarget) {
122   } else if (!isPointerDefinition && !IsVariableName(ultimate)) {
123     return BlameSymbol(at, "'%s' is not a variable"_en_US, original);
124   } else if (IsProtected(ultimate) && IsUseAssociated(original, scope)) {
125     return BlameSymbol(at, "'%s' is protected in this scope"_en_US, original);
126   } else if (IsIntentIn(ultimate) &&
127       (!IsPointer(ultimate) || (isWholeSymbol && isPointerDefinition))) {
128     return BlameSymbol(
129         at, "'%s' is an INTENT(IN) dummy argument"_en_US, original);
130   } else if (acceptAllocatable && IsAllocatable(ultimate) &&
131       !flags.test(DefinabilityFlag::SourcedAllocation)) {
132     // allocating a function result doesn't count as a def'n
133     // unless there's SOURCE=
134   } else if (!flags.test(DefinabilityFlag::DoNotNoteDefinition)) {
135     scope.context().NoteDefinedSymbol(ultimate);
136   }
137   if (const Scope * pure{FindPureProcedureContaining(scope)}) {
138     // Additional checking for pure subprograms.
139     if (!isTargetDefinition || isComponentPointerTarget) {
140       if (auto msg{CheckDefinabilityInPureScope(
141               at, original, ultimate, scope, *pure)}) {
142         return msg;
143       }
144     }
145     if (const Symbol *
146         visible{FindExternallyVisibleObject(
147             ultimate, *pure, isPointerDefinition)}) {
148       return BlameSymbol(at,
149           "'%s' is externally visible via '%s' and not definable in a pure subprogram"_en_US,
150           original, visible->name());
151     }
152   }
153   if (const Scope * deviceContext{FindCUDADeviceContext(&scope)}) {
154     bool isOwnedByDeviceCode{deviceContext->Contains(ultimate.owner())};
155     if (isPointerDefinition && !acceptAllocatable) {
156       return BlameSymbol(at,
157           "'%s' is a pointer and may not be associated in a device subprogram"_err_en_US,
158           original);
159     } else if (auto cudaDataAttr{GetCUDADataAttr(&ultimate)}) {
160       if (*cudaDataAttr == common::CUDADataAttr::Constant) {
161         return BlameSymbol(at,
162             "'%s' has ATTRIBUTES(CONSTANT) and is not definable in a device subprogram"_err_en_US,
163             original);
164       } else if (acceptAllocatable && !isOwnedByDeviceCode) {
165         return BlameSymbol(at,
166             "'%s' is a host-associated allocatable and is not definable in a device subprogram"_err_en_US,
167             original);
168       } else if (*cudaDataAttr != common::CUDADataAttr::Device &&
169           *cudaDataAttr != common::CUDADataAttr::Managed &&
170           *cudaDataAttr != common::CUDADataAttr::Shared) {
171         return BlameSymbol(at,
172             "'%s' is not device or managed or shared data and is not definable in a device subprogram"_err_en_US,
173             original);
174       }
175     } else if (!isOwnedByDeviceCode) {
176       return BlameSymbol(at,
177           "'%s' is a host variable and is not definable in a device subprogram"_err_en_US,
178           original);
179     }
180   }
181   return std::nullopt;
182 }
183 
184 static std::optional<parser::Message> WhyNotDefinableLast(parser::CharBlock at,
185     const Scope &scope, DefinabilityFlags flags, const Symbol &original) {
186   const Symbol &ultimate{original.GetUltimate()};
187   if (const auto *association{ultimate.detailsIf<AssocEntityDetails>()};
188       association &&
189       (association->rank().has_value() ||
190           !flags.test(DefinabilityFlag::PointerDefinition))) {
191     if (auto dataRef{
192             evaluate::ExtractDataRef(*association->expr(), true, true)}) {
193       return WhyNotDefinableLast(at, scope, flags, dataRef->GetLastSymbol());
194     }
195   }
196   if (flags.test(DefinabilityFlag::PointerDefinition)) {
197     if (flags.test(DefinabilityFlag::AcceptAllocatable)) {
198       if (!IsAllocatableOrObjectPointer(&ultimate)) {
199         return BlameSymbol(
200             at, "'%s' is neither a pointer nor an allocatable"_en_US, original);
201       }
202     } else if (!IsPointer(ultimate)) {
203       return BlameSymbol(at, "'%s' is not a pointer"_en_US, original);
204     }
205     return std::nullopt; // pointer assignment - skip following checks
206   }
207   if (!flags.test(DefinabilityFlag::AllowEventLockOrNotifyType) &&
208       IsOrContainsEventOrLockComponent(ultimate)) {
209     return BlameSymbol(at,
210         "'%s' is an entity with either an EVENT_TYPE or LOCK_TYPE"_en_US,
211         original);
212   }
213   if (FindPureProcedureContaining(scope)) {
214     if (auto dyType{evaluate::DynamicType::From(ultimate)}) {
215       if (!flags.test(DefinabilityFlag::PolymorphicOkInPure)) {
216         if (dyType->IsPolymorphic()) { // C1596
217           return BlameSymbol(
218               at, "'%s' is polymorphic in a pure subprogram"_en_US, original);
219         }
220       }
221       if (const Symbol * impure{HasImpureFinal(ultimate)}) {
222         return BlameSymbol(at, "'%s' has an impure FINAL procedure '%s'"_en_US,
223             original, impure->name());
224       }
225       if (const DerivedTypeSpec * derived{GetDerivedTypeSpec(dyType)}) {
226         if (!flags.test(DefinabilityFlag::PolymorphicOkInPure)) {
227           if (auto bad{
228                   FindPolymorphicAllocatablePotentialComponent(*derived)}) {
229             return BlameSymbol(at,
230                 "'%s' has polymorphic component '%s' in a pure subprogram"_en_US,
231                 original, bad.BuildResultDesignatorName());
232           }
233         }
234       }
235     }
236   }
237   return std::nullopt;
238 }
239 
240 // Checks a data-ref
241 static std::optional<parser::Message> WhyNotDefinable(parser::CharBlock at,
242     const Scope &scope, DefinabilityFlags flags,
243     const evaluate::DataRef &dataRef) {
244   auto whyNotBase{
245       WhyNotDefinableBase(at, scope, flags, dataRef.GetFirstSymbol(),
246           std::holds_alternative<evaluate::SymbolRef>(dataRef.u),
247           DefinesComponentPointerTarget(dataRef, flags))};
248   if (!whyNotBase || !whyNotBase->IsFatal()) {
249     if (auto whyNotLast{
250             WhyNotDefinableLast(at, scope, flags, dataRef.GetLastSymbol())}) {
251       if (whyNotLast->IsFatal() || !whyNotBase) {
252         return whyNotLast;
253       }
254     }
255   }
256   return whyNotBase;
257 }
258 
259 std::optional<parser::Message> WhyNotDefinable(parser::CharBlock at,
260     const Scope &scope, DefinabilityFlags flags, const Symbol &original) {
261   auto whyNotBase{WhyNotDefinableBase(at, scope, flags, original,
262       /*isWholeSymbol=*/true, /*isComponentPointerTarget=*/false)};
263   if (!whyNotBase || !whyNotBase->IsFatal()) {
264     if (auto whyNotLast{WhyNotDefinableLast(at, scope, flags, original)}) {
265       if (whyNotLast->IsFatal() || !whyNotBase) {
266         return whyNotLast;
267       }
268     }
269   }
270   return whyNotBase;
271 }
272 
273 class DuplicatedSubscriptFinder
274     : public evaluate::AnyTraverse<DuplicatedSubscriptFinder, bool> {
275   using Base = evaluate::AnyTraverse<DuplicatedSubscriptFinder, bool>;
276 
277 public:
278   explicit DuplicatedSubscriptFinder(evaluate::FoldingContext &foldingContext)
279       : Base{*this}, foldingContext_{foldingContext} {}
280   using Base::operator();
281   bool operator()(const evaluate::ActualArgument &) {
282     return false; // don't descend into argument expressions
283   }
284   bool operator()(const evaluate::ArrayRef &aRef) {
285     bool anyVector{false};
286     for (const auto &ss : aRef.subscript()) {
287       if (ss.Rank() > 0) {
288         anyVector = true;
289         if (const auto *vecExpr{
290                 std::get_if<evaluate::IndirectSubscriptIntegerExpr>(&ss.u)}) {
291           auto folded{evaluate::Fold(foldingContext_,
292               evaluate::Expr<evaluate::SubscriptInteger>{vecExpr->value()})};
293           if (const auto *con{
294                   evaluate::UnwrapConstantValue<evaluate::SubscriptInteger>(
295                       folded)}) {
296             std::set<std::int64_t> values;
297             for (const auto &j : con->values()) {
298               if (auto pair{values.emplace(j.ToInt64())}; !pair.second) {
299                 return true; // duplicate
300               }
301             }
302           }
303           return false;
304         }
305       }
306     }
307     return anyVector ? false : (*this)(aRef.base());
308   }
309 
310 private:
311   evaluate::FoldingContext &foldingContext_;
312 };
313 
314 std::optional<parser::Message> WhyNotDefinable(parser::CharBlock at,
315     const Scope &scope, DefinabilityFlags flags,
316     const evaluate::Expr<evaluate::SomeType> &expr) {
317   std::optional<parser::Message> portabilityWarning;
318   if (auto dataRef{evaluate::ExtractDataRef(expr, true, true)}) {
319     if (evaluate::HasVectorSubscript(expr)) {
320       if (flags.test(DefinabilityFlag::VectorSubscriptIsOk)) {
321         if (auto type{expr.GetType()}) {
322           if (!type->IsUnlimitedPolymorphic() &&
323               type->category() == TypeCategory::Derived) {
324             // Seek the FINAL subroutine that should but cannot be called
325             // for this definition of an array with a vector-valued subscript.
326             // If there's an elemental FINAL subroutine, all is well; otherwise,
327             // if there is a FINAL subroutine with a matching or assumed rank
328             // dummy argument, there's no way to call it.
329             int rank{expr.Rank()};
330             const DerivedTypeSpec *spec{&type->GetDerivedTypeSpec()};
331             while (spec) {
332               bool anyElemental{false};
333               const Symbol *anyRankMatch{nullptr};
334               for (auto ref : FinalsForDerivedTypeInstantiation(*spec)) {
335                 const Symbol &ultimate{ref->GetUltimate()};
336                 anyElemental |= ultimate.attrs().test(Attr::ELEMENTAL);
337                 if (const auto *subp{ultimate.detailsIf<SubprogramDetails>()}) {
338                   if (!subp->dummyArgs().empty()) {
339                     if (const Symbol * arg{subp->dummyArgs()[0]}) {
340                       const auto *object{arg->detailsIf<ObjectEntityDetails>()};
341                       if (arg->Rank() == rank ||
342                           (object && object->IsAssumedRank())) {
343                         anyRankMatch = &*ref;
344                       }
345                     }
346                   }
347                 }
348               }
349               if (anyRankMatch && !anyElemental) {
350                 if (!portabilityWarning &&
351                     scope.context().languageFeatures().ShouldWarn(
352                         common::UsageWarning::VectorSubscriptFinalization)) {
353                   portabilityWarning = parser::Message{
354                       common::UsageWarning::VectorSubscriptFinalization, at,
355                       "Variable '%s' has a vector subscript and will be finalized by non-elemental subroutine '%s'"_port_en_US,
356                       expr.AsFortran(), anyRankMatch->name()};
357                 }
358                 break;
359               }
360               const auto *parent{FindParentTypeSpec(*spec)};
361               spec = parent ? parent->AsDerived() : nullptr;
362             }
363           }
364         }
365         if (!flags.test(DefinabilityFlag::DuplicatesAreOk) &&
366             DuplicatedSubscriptFinder{scope.context().foldingContext()}(expr)) {
367           return parser::Message{at,
368               "Variable has a vector subscript with a duplicated element"_err_en_US};
369         }
370       } else {
371         return parser::Message{at,
372             "Variable '%s' has a vector subscript"_err_en_US, expr.AsFortran()};
373       }
374     }
375     if (FindPureProcedureContaining(scope) &&
376         evaluate::ExtractCoarrayRef(expr)) {
377       return parser::Message(at,
378           "A pure subprogram may not define the coindexed object '%s'"_err_en_US,
379           expr.AsFortran());
380     }
381     if (auto whyNotDataRef{WhyNotDefinable(at, scope, flags, *dataRef)}) {
382       return whyNotDataRef;
383     }
384   } else if (evaluate::IsNullPointer(expr)) {
385     return parser::Message{
386         at, "'%s' is a null pointer"_err_en_US, expr.AsFortran()};
387   } else if (flags.test(DefinabilityFlag::PointerDefinition)) {
388     if (const auto *procDesignator{
389             std::get_if<evaluate::ProcedureDesignator>(&expr.u)}) {
390       // Defining a procedure pointer
391       if (const Symbol * procSym{procDesignator->GetSymbol()}) {
392         if (evaluate::ExtractCoarrayRef(expr)) { // C1027
393           return BlameSymbol(at,
394               "Procedure pointer '%s' may not be a coindexed object"_err_en_US,
395               *procSym, expr.AsFortran());
396         }
397         if (const auto *component{procDesignator->GetComponent()}) {
398           flags.reset(DefinabilityFlag::PointerDefinition);
399           return WhyNotDefinableBase(at, scope, flags,
400               component->base().GetFirstSymbol(), false,
401               DefinesComponentPointerTarget(component->base(), flags));
402         } else {
403           return WhyNotDefinable(at, scope, flags, *procSym);
404         }
405       }
406     }
407     return parser::Message{
408         at, "'%s' is not a definable pointer"_err_en_US, expr.AsFortran()};
409   } else if (!evaluate::IsVariable(expr)) {
410     return parser::Message{
411         at, "'%s' is not a variable or pointer"_err_en_US, expr.AsFortran()};
412   }
413   return portabilityWarning;
414 }
415 
416 } // namespace Fortran::semantics
417