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(x: ultimate, scope: 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 | auto dyType{evaluate::DynamicType::From(ultimate)}; |
197 | const auto *inPure{FindPureProcedureContaining(scope)}; |
198 | if (inPure && !flags.test(DefinabilityFlag::PolymorphicOkInPure) && |
199 | flags.test(DefinabilityFlag::PotentialDeallocation) && dyType && |
200 | dyType->IsPolymorphic()) { |
201 | return BlameSymbol(at, |
202 | "'%s' is a whole polymorphic object in a pure subprogram"_en_US , |
203 | original); |
204 | } |
205 | if (flags.test(DefinabilityFlag::PointerDefinition)) { |
206 | if (flags.test(DefinabilityFlag::AcceptAllocatable)) { |
207 | if (!IsAllocatableOrObjectPointer(&ultimate)) { |
208 | return BlameSymbol( |
209 | at, "'%s' is neither a pointer nor an allocatable"_en_US , original); |
210 | } |
211 | } else if (!IsPointer(ultimate)) { |
212 | return BlameSymbol(at, "'%s' is not a pointer"_en_US , original); |
213 | } |
214 | return std::nullopt; // pointer assignment - skip following checks |
215 | } |
216 | if (!flags.test(DefinabilityFlag::AllowEventLockOrNotifyType) && |
217 | IsOrContainsEventOrLockComponent(ultimate)) { |
218 | return BlameSymbol(at, |
219 | "'%s' is an entity with either an EVENT_TYPE or LOCK_TYPE"_en_US , |
220 | original); |
221 | } |
222 | if (dyType && inPure) { |
223 | if (const Symbol * impure{HasImpureFinal(ultimate)}) { |
224 | return BlameSymbol(at, "'%s' has an impure FINAL procedure '%s'"_en_US , |
225 | original, impure->name()); |
226 | } |
227 | if (!flags.test(DefinabilityFlag::PolymorphicOkInPure)) { |
228 | if (const DerivedTypeSpec * derived{GetDerivedTypeSpec(dyType)}) { |
229 | if (auto bad{FindPolymorphicAllocatablePotentialComponent(*derived)}) { |
230 | return BlameSymbol(at, |
231 | "'%s' has polymorphic component '%s' in a pure subprogram"_en_US , |
232 | original, bad.BuildResultDesignatorName()); |
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 | evaluate::UnwrapWholeSymbolDataRef(dataRef) != nullptr, |
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::IsNullPointerOrAllocatable(&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 | |