[flang] Refine "same type" testing for intrinsic arguments (#125133)
[llvm-project.git] / flang / lib / Semantics / definable.cpp
blob6d0155c24c31abd6bb1786e195539386fe96b0cb
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 //===----------------------------------------------------------------------===//
9 #include "definable.h"
10 #include "flang/Evaluate/tools.h"
11 #include "flang/Semantics/tools.h"
13 using namespace Fortran::parser::literals;
15 namespace Fortran::semantics {
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;
26 static bool IsPointerDummyOfPureFunction(const Symbol &x) {
27 return IsPointerDummy(x) && FindPureProcedureContaining(x.owner()) &&
28 x.owner().symbol() && IsFunction(*x.owner().symbol());
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;
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);
60 return std::nullopt;
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;
74 [](const evaluate::Component &component) { return &component; },
75 [](const evaluate::ArrayRef &aRef) {
76 return aRef.base().UnwrapComponent();
78 [](const evaluate::CoarrayRef &aRef)
79 -> const evaluate::Component * { return nullptr; },
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;
90 flags.reset(DefinabilityFlag::PointerDefinition);
91 return DefinesComponentPointerTarget(component->base(), flags);
92 } else {
93 return false;
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));
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);
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;
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());
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);
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);
181 return std::nullopt;
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());
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);
202 } else if (!IsPointer(ultimate)) {
203 return BlameSymbol(at, "'%s' is not a pointer"_en_US, original);
205 return std::nullopt; // pointer assignment - skip following checks
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);
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);
221 if (const Symbol * impure{HasImpureFinal(ultimate)}) {
222 return BlameSymbol(at, "'%s' has an impure FINAL procedure '%s'"_en_US,
223 original, impure->name());
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());
237 return std::nullopt;
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;
256 return whyNotBase;
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;
270 return whyNotBase;
273 class DuplicatedSubscriptFinder
274 : public evaluate::AnyTraverse<DuplicatedSubscriptFinder, bool> {
275 using Base = evaluate::AnyTraverse<DuplicatedSubscriptFinder, bool>;
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
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
303 return false;
307 return anyVector ? false : (*this)(aRef.base());
310 private:
311 evaluate::FoldingContext &foldingContext_;
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;
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()};
358 break;
360 const auto *parent{FindParentTypeSpec(*spec)};
361 spec = parent ? parent->AsDerived() : nullptr;
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};
370 } else {
371 return parser::Message{at,
372 "Variable '%s' has a vector subscript"_err_en_US, expr.AsFortran()};
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());
381 if (auto whyNotDataRef{WhyNotDefinable(at, scope, flags, *dataRef)}) {
382 return whyNotDataRef;
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());
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);
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()};
413 return portabilityWarning;
416 } // namespace Fortran::semantics