Skip to content

Commit aad5984

Browse files
committed
[flang] Portability warnings for an ambiguous ASSOCIATED() case
The standard's specification for the ASSOCIATED() intrinsic function describes its optional second argument (TARGET=) as being required to be a valid target for a pointer assignment statement in which the first argument (POINTER=) was the left-hand side. Some Fortran compilers apparently interpret this text as a requirement that the POINTER= argument actually be a valid left-hand side to a pointer assignment statement, and emit an error if it is not so. This particularly affects the use of an explicit NULL pointer as the first argument. Such usage is well-defined, benign, useful, and supported by at least two other compilers, so we should continue to accept it. This patch adds a portability warning and some documentation. In order to implement the portability warning in the best way, the special checks on calls to the ASSOCIATED() intrinsic function have been moved from intrinsic processing to Semantics/check-calls.cpp, whence they have access to semantics' toolchest. Special checks for other intrinsic functions might also migrate in the future in order to keep them all in one place. Differential Revision: https://reviews.llvm.org/D142768
1 parent b4b9786 commit aad5984

File tree

10 files changed

+239
-182
lines changed

10 files changed

+239
-182
lines changed

flang/docs/Extensions.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,19 @@ end module
527527
scope, with a portability warning, since that global name is not actually
528528
capable of being "used" in its scope.
529529

530+
* In the definition of the `ASSOCIATED` intrinsic function (16.9.16), its optional
531+
second argument `TARGET=` is required to be "allowable as the data-target or
532+
proc-target in a pointer assignment statement (10.2.2) in which POINTER is
533+
data-pointer-object or proc-pointer-object." Some Fortran compilers
534+
interpret this to require that the first argument (`POINTER=`) be a valid
535+
left-hand side for a pointer assignment statement -- in particular, it
536+
cannot be `NULL()`, but also it is required to be modifiable.
537+
As there is no good reason to disallow (say) an `INTENT(IN)` pointer here,
538+
or even `NULL()` as a well-defined case that is always `.FALSE.`,
539+
this compiler doesn't require the `POINTER=` argument to be a valid
540+
left-hand side for a pointer assignment statement, and we emit a
541+
portability warning when it is not.
542+
530543
## De Facto Standard Features
531544

532545
* `EXTENDS_TYPE_OF()` returns `.TRUE.` if both of its arguments have the

flang/include/flang/Evaluate/tools.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -970,6 +970,7 @@ bool IsAllocatableDesignator(const Expr<SomeType> &);
970970
// Procedure and pointer detection predicates
971971
bool IsProcedure(const Expr<SomeType> &);
972972
bool IsFunction(const Expr<SomeType> &);
973+
bool IsProcedurePointer(const Expr<SomeType> &);
973974
bool IsProcedurePointerTarget(const Expr<SomeType> &);
974975
bool IsBareNullPointer(const Expr<SomeType> *); // NULL() w/o MOLD= or type
975976
bool IsNullObjectPointer(const Expr<SomeType> &);

flang/lib/Evaluate/intrinsics.cpp

Lines changed: 3 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -2656,129 +2656,6 @@ IntrinsicProcTable::Implementation::HandleC_F_Pointer(
26562656
}
26572657
}
26582658

2659-
static bool CheckAssociated(SpecificCall &call, FoldingContext &context) {
2660-
bool ok{true};
2661-
if (const auto &pointerArg{call.arguments[0]}) {
2662-
if (const auto *pointerExpr{pointerArg->UnwrapExpr()}) {
2663-
if (const Symbol *pointerSymbol{GetLastSymbol(*pointerExpr)}) {
2664-
if (!pointerSymbol->attrs().test(semantics::Attr::POINTER)) {
2665-
AttachDeclaration(context.messages().Say(pointerArg->sourceLocation(),
2666-
"POINTER= argument of ASSOCIATED() must be a "
2667-
"POINTER"_err_en_US),
2668-
*pointerSymbol);
2669-
} else {
2670-
if (const auto &targetArg{call.arguments[1]}) {
2671-
if (const auto *targetExpr{targetArg->UnwrapExpr()}) {
2672-
std::optional<characteristics::Procedure> pointerProc, targetProc;
2673-
const auto *targetProcDesignator{
2674-
UnwrapExpr<ProcedureDesignator>(*targetExpr)};
2675-
const Symbol *targetSymbol{GetLastSymbol(*targetExpr)};
2676-
bool isCall{false};
2677-
std::string targetName;
2678-
if (const auto *targetProcRef{// target is a function call
2679-
std::get_if<ProcedureRef>(&targetExpr->u)}) {
2680-
if (auto targetRefedChars{
2681-
characteristics::Procedure::Characterize(
2682-
*targetProcRef, context)}) {
2683-
targetProc = *targetRefedChars;
2684-
targetName = targetProcRef->proc().GetName() + "()";
2685-
isCall = true;
2686-
}
2687-
} else if (targetProcDesignator) {
2688-
targetProc = characteristics::Procedure::Characterize(
2689-
*targetProcDesignator, context);
2690-
targetName = targetProcDesignator->GetName();
2691-
} else if (targetSymbol) {
2692-
if (IsProcedure(*targetSymbol)) {
2693-
// proc that's not a call
2694-
targetProc = characteristics::Procedure::Characterize(
2695-
*targetSymbol, context);
2696-
}
2697-
targetName = targetSymbol->name().ToString();
2698-
}
2699-
if (IsProcedure(*pointerSymbol)) {
2700-
pointerProc = characteristics::Procedure::Characterize(
2701-
*pointerSymbol, context);
2702-
}
2703-
if (pointerProc) {
2704-
if (targetProc) {
2705-
// procedure pointer and procedure target
2706-
std::string whyNot;
2707-
const SpecificIntrinsic *specificIntrinsic{nullptr};
2708-
if (targetProcDesignator) {
2709-
specificIntrinsic =
2710-
targetProcDesignator->GetSpecificIntrinsic();
2711-
}
2712-
if (std::optional<parser::MessageFixedText> msg{
2713-
CheckProcCompatibility(isCall, pointerProc,
2714-
&*targetProc, specificIntrinsic, whyNot)}) {
2715-
msg->set_severity(parser::Severity::Warning);
2716-
AttachDeclaration(
2717-
context.messages().Say(std::move(*msg),
2718-
"pointer '" + pointerSymbol->name().ToString() +
2719-
"'",
2720-
targetName, whyNot),
2721-
*pointerSymbol);
2722-
}
2723-
} else if (!IsNullProcedurePointer(*targetExpr)) {
2724-
// procedure pointer and object target
2725-
AttachDeclaration(
2726-
context.messages().Say(
2727-
"POINTER= argument '%s' is a procedure "
2728-
"pointer but the TARGET= argument '%s' is not a "
2729-
"procedure or procedure pointer"_err_en_US,
2730-
pointerSymbol->name(), targetName),
2731-
*pointerSymbol);
2732-
}
2733-
} else if (targetProc) {
2734-
// object pointer and procedure target
2735-
AttachDeclaration(
2736-
context.messages().Say(
2737-
"POINTER= argument '%s' is an object pointer "
2738-
"but the TARGET= argument '%s' is a "
2739-
"procedure designator"_err_en_US,
2740-
pointerSymbol->name(), targetName),
2741-
*pointerSymbol);
2742-
} else if (targetSymbol) {
2743-
// object pointer and target
2744-
SymbolVector symbols{GetSymbolVector(*targetExpr)};
2745-
CHECK(!symbols.empty());
2746-
if (!GetLastTarget(symbols)) {
2747-
parser::Message *msg{context.messages().Say(
2748-
targetArg->sourceLocation(),
2749-
"TARGET= argument '%s' must have either the POINTER or the TARGET attribute"_err_en_US,
2750-
targetExpr->AsFortran())};
2751-
for (SymbolRef ref : symbols) {
2752-
msg = AttachDeclaration(msg, *ref);
2753-
}
2754-
} else if (HasVectorSubscript(*targetExpr) ||
2755-
ExtractCoarrayRef(*targetExpr)) {
2756-
context.messages().Say(targetArg->sourceLocation(),
2757-
"TARGET= argument '%s' may not have a vector subscript or coindexing"_err_en_US,
2758-
targetExpr->AsFortran());
2759-
}
2760-
if (const auto pointerType{pointerArg->GetType()}) {
2761-
if (const auto targetType{targetArg->GetType()}) {
2762-
ok = pointerType->IsTkCompatibleWith(*targetType);
2763-
}
2764-
}
2765-
}
2766-
}
2767-
}
2768-
}
2769-
}
2770-
}
2771-
} else {
2772-
// No arguments to ASSOCIATED()
2773-
ok = false;
2774-
}
2775-
if (!ok) {
2776-
context.messages().Say(
2777-
"Arguments of ASSOCIATED() must be a POINTER and an optional valid target"_err_en_US);
2778-
}
2779-
return ok;
2780-
}
2781-
27822659
static bool CheckForNonPositiveValues(FoldingContext &context,
27832660
const ActualArgument &arg, const std::string &procName,
27842661
const std::string &argName) {
@@ -2875,6 +2752,8 @@ static bool CheckAtomicDefineAndRef(FoldingContext &context,
28752752
}
28762753

28772754
// Applies any semantic checks peculiar to an intrinsic.
2755+
// TODO: Move the rest of these checks to Semantics/check-call.cpp, which is
2756+
// where ASSOCIATED() is now validated.
28782757
static bool ApplySpecificChecks(SpecificCall &call, FoldingContext &context) {
28792758
bool ok{true};
28802759
const std::string &name{call.specificIntrinsic.name};
@@ -2891,7 +2770,7 @@ static bool ApplySpecificChecks(SpecificCall &call, FoldingContext &context) {
28912770
"Argument of ALLOCATED() must be an ALLOCATABLE object or component"_err_en_US);
28922771
}
28932772
} else if (name == "associated") {
2894-
return CheckAssociated(call, context);
2773+
// Now handled in Semantics/check-call.cpp
28952774
} else if (name == "atomic_and" || name == "atomic_or" ||
28962775
name == "atomic_xor") {
28972776
return CheckForCoindexedObject(context, call.arguments[2], name, "stat");

flang/lib/Evaluate/tools.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,18 @@ bool IsFunction(const Expr<SomeType> &expr) {
737737
return designator && designator->GetType().has_value();
738738
}
739739

740+
bool IsProcedurePointer(const Expr<SomeType> &expr) {
741+
return common::visit(common::visitors{
742+
[](const NullPointer &) { return true; },
743+
[](const ProcedureRef &) { return false; },
744+
[&](const auto &) {
745+
const Symbol *last{GetLastSymbol(expr)};
746+
return last && IsProcedurePointer(*last);
747+
},
748+
},
749+
expr.u);
750+
}
751+
740752
bool IsProcedurePointerTarget(const Expr<SomeType> &expr) {
741753
return common::visit(common::visitors{
742754
[](const NullPointer &) { return true; },

0 commit comments

Comments
 (0)