Skip to content

Commit b28689e

Browse files
klauslerjustinfargnoli
authored andcommitted
[flang] Catch name resolution error due to global scoping (llvm#77683)
In CALL FOO PRINT *, ABS(FOO) we currently resolve the first FOO to a global external subprogram, but then the second FOO is treated as an implicitly typed local variable. This happens because the name FOO is not present in the local scope. Fix by adding FOO to the local scope using a place-holding HostAssocDetails symbol whose existence prevents the creation of another FOO in the local scope. The symbol stored in the parser::Name parse tree nodes or used in typed expressions will all continue to point to the global external subprogram. Resolves llvm-test-suite/Fortran/gfortran/regression/pr71859.f90.
1 parent a1dced9 commit b28689e

File tree

9 files changed

+132
-41
lines changed

9 files changed

+132
-41
lines changed

flang/include/flang/Evaluate/characteristics.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ struct DummyDataObject {
227227
std::optional<std::string> *warning = nullptr) const;
228228
static std::optional<DummyDataObject> Characterize(
229229
const semantics::Symbol &, FoldingContext &);
230-
bool CanBePassedViaImplicitInterface() const;
230+
bool CanBePassedViaImplicitInterface(std::string *whyNot = nullptr) const;
231231
llvm::raw_ostream &Dump(llvm::raw_ostream &) const;
232232

233233
TypeAndShape type;
@@ -248,7 +248,7 @@ struct DummyProcedure {
248248
bool operator!=(const DummyProcedure &that) const { return !(*this == that); }
249249
bool IsCompatibleWith(
250250
const DummyProcedure &, std::string *whyNot = nullptr) const;
251-
bool CanBePassedViaImplicitInterface() const;
251+
bool CanBePassedViaImplicitInterface(std::string *whyNot = nullptr) const;
252252
llvm::raw_ostream &Dump(llvm::raw_ostream &) const;
253253

254254
CopyableIndirection<Procedure> procedure;
@@ -282,7 +282,7 @@ struct DummyArgument {
282282
void SetOptional(bool = true);
283283
common::Intent GetIntent() const;
284284
void SetIntent(common::Intent);
285-
bool CanBePassedViaImplicitInterface() const;
285+
bool CanBePassedViaImplicitInterface(std::string *whyNot = nullptr) const;
286286
bool IsTypelessIntrinsicDummy() const;
287287
bool IsCompatibleWith(const DummyArgument &, std::string *whyNot = nullptr,
288288
std::optional<std::string> *warning = nullptr) const;
@@ -325,7 +325,7 @@ struct FunctionResult {
325325
return std::get_if<TypeAndShape>(&u);
326326
}
327327
void SetType(DynamicType t) { std::get<TypeAndShape>(u).set_type(t); }
328-
bool CanBeReturnedViaImplicitInterface() const;
328+
bool CanBeReturnedViaImplicitInterface(std::string *whyNot = nullptr) const;
329329
bool IsCompatibleWith(
330330
const FunctionResult &, std::string *whyNot = nullptr) const;
331331

@@ -377,7 +377,7 @@ struct Procedure {
377377
return !attrs.test(Attr::ImplicitInterface);
378378
}
379379
int FindPassIndex(std::optional<parser::CharBlock>) const;
380-
bool CanBeCalledViaImplicitInterface() const;
380+
bool CanBeCalledViaImplicitInterface(std::string *whyNot = nullptr) const;
381381
bool CanOverride(const Procedure &, std::optional<int> passIndex) const;
382382
bool IsCompatibleWith(const Procedure &, std::string *whyNot = nullptr,
383383
const SpecificIntrinsic * = nullptr,

flang/lib/Evaluate/characteristics.cpp

Lines changed: 82 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -417,24 +417,45 @@ std::optional<DummyDataObject> DummyDataObject::Characterize(
417417
return std::nullopt;
418418
}
419419

420-
bool DummyDataObject::CanBePassedViaImplicitInterface() const {
420+
bool DummyDataObject::CanBePassedViaImplicitInterface(
421+
std::string *whyNot) const {
421422
if ((attrs &
422423
Attrs{Attr::Allocatable, Attr::Asynchronous, Attr::Optional,
423424
Attr::Pointer, Attr::Target, Attr::Value, Attr::Volatile})
424425
.any()) {
426+
if (whyNot) {
427+
*whyNot = "a dummy argument has the allocatable, asynchronous, optional, "
428+
"pointer, target, value, or volatile attribute";
429+
}
425430
return false; // 15.4.2.2(3)(a)
426431
} else if ((type.attrs() &
427432
TypeAndShape::Attrs{TypeAndShape::Attr::AssumedShape,
428433
TypeAndShape::Attr::AssumedRank,
429434
TypeAndShape::Attr::Coarray})
430435
.any()) {
436+
if (whyNot) {
437+
*whyNot = "a dummy argument is assumed-shape, assumed-rank, or a coarray";
438+
}
431439
return false; // 15.4.2.2(3)(b-d)
432440
} else if (type.type().IsPolymorphic()) {
441+
if (whyNot) {
442+
*whyNot = "a dummy argument is polymorphic";
443+
}
433444
return false; // 15.4.2.2(3)(f)
434445
} else if (cudaDataAttr) {
446+
if (whyNot) {
447+
*whyNot = "a dummy argument has a CUDA data attribute";
448+
}
435449
return false;
436450
} else if (const auto *derived{GetDerivedTypeSpec(type.type())}) {
437-
return derived->parameters().empty(); // 15.4.2.2(3)(e)
451+
if (derived->parameters().empty()) { // 15.4.2.2(3)(e)
452+
return true;
453+
} else {
454+
if (whyNot) {
455+
*whyNot = "a dummy argument has derived type parameters";
456+
}
457+
return false;
458+
}
438459
} else {
439460
return true;
440461
}
@@ -493,8 +514,12 @@ bool DummyProcedure::IsCompatibleWith(
493514
return true;
494515
}
495516

496-
bool DummyProcedure::CanBePassedViaImplicitInterface() const {
517+
bool DummyProcedure::CanBePassedViaImplicitInterface(
518+
std::string *whyNot) const {
497519
if ((attrs & Attrs{Attr::Optional, Attr::Pointer}).any()) {
520+
if (whyNot) {
521+
*whyNot = "a dummy procedure is optional or a pointer";
522+
}
498523
return false; // 15.4.2.2(3)(a)
499524
}
500525
return true;
@@ -895,11 +920,11 @@ common::Intent DummyArgument::GetIntent() const {
895920
u);
896921
}
897922

898-
bool DummyArgument::CanBePassedViaImplicitInterface() const {
923+
bool DummyArgument::CanBePassedViaImplicitInterface(std::string *whyNot) const {
899924
if (const auto *object{std::get_if<DummyDataObject>(&u)}) {
900-
return object->CanBePassedViaImplicitInterface();
925+
return object->CanBePassedViaImplicitInterface(whyNot);
901926
} else if (const auto *proc{std::get_if<DummyProcedure>(&u)}) {
902-
return proc->CanBePassedViaImplicitInterface();
927+
return proc->CanBePassedViaImplicitInterface(whyNot);
903928
} else {
904929
return true;
905930
}
@@ -970,13 +995,23 @@ bool FunctionResult::IsAssumedLengthCharacter() const {
970995
}
971996
}
972997

973-
bool FunctionResult::CanBeReturnedViaImplicitInterface() const {
998+
bool FunctionResult::CanBeReturnedViaImplicitInterface(
999+
std::string *whyNot) const {
9741000
if (attrs.test(Attr::Pointer) || attrs.test(Attr::Allocatable)) {
1001+
if (whyNot) {
1002+
*whyNot = "the function result is a pointer or allocatable";
1003+
}
9751004
return false; // 15.4.2.2(4)(b)
9761005
} else if (cudaDataAttr) {
1006+
if (whyNot) {
1007+
*whyNot = "the function result has CUDA attributes";
1008+
}
9771009
return false;
9781010
} else if (const auto *typeAndShape{GetTypeAndShape()}) {
9791011
if (typeAndShape->Rank() > 0) {
1012+
if (whyNot) {
1013+
*whyNot = "the function result is an array";
1014+
}
9801015
return false; // 15.4.2.2(4)(a)
9811016
} else {
9821017
const DynamicType &type{typeAndShape->type()};
@@ -986,31 +1021,52 @@ bool FunctionResult::CanBeReturnedViaImplicitInterface() const {
9861021
return true;
9871022
} else if (const auto *param{type.charLengthParamValue()}) {
9881023
if (const auto &expr{param->GetExplicit()}) {
989-
return IsConstantExpr(*expr); // 15.4.2.2(4)(c)
1024+
if (IsConstantExpr(*expr)) { // 15.4.2.2(4)(c)
1025+
return true;
1026+
} else {
1027+
if (whyNot) {
1028+
*whyNot = "the function result's length is not constant";
1029+
}
1030+
return false;
1031+
}
9901032
} else if (param->isAssumed()) {
9911033
return true;
9921034
}
9931035
}
1036+
if (whyNot) {
1037+
*whyNot = "the function result's length is not known to the caller";
1038+
}
9941039
return false;
9951040
case TypeCategory::Derived:
996-
if (!type.IsPolymorphic()) {
1041+
if (type.IsPolymorphic()) {
1042+
if (whyNot) {
1043+
*whyNot = "the function result is polymorphic";
1044+
}
1045+
return false;
1046+
} else {
9971047
const auto &spec{type.GetDerivedTypeSpec()};
9981048
for (const auto &pair : spec.parameters()) {
9991049
if (const auto &expr{pair.second.GetExplicit()}) {
10001050
if (!IsConstantExpr(*expr)) {
1051+
if (whyNot) {
1052+
*whyNot = "the function result's derived type has a "
1053+
"non-constant parameter";
1054+
}
10011055
return false; // 15.4.2.2(4)(c)
10021056
}
10031057
}
10041058
}
10051059
return true;
10061060
}
1007-
return false;
10081061
default:
10091062
return true;
10101063
}
10111064
}
10121065
} else {
1013-
return false; // 15.4.2.2(4)(b) - procedure pointer
1066+
if (whyNot) {
1067+
*whyNot = "the function result has unknown type or shape";
1068+
}
1069+
return false; // 15.4.2.2(4)(b) - procedure pointer?
10141070
}
10151071
}
10161072

@@ -1341,20 +1397,30 @@ std::optional<Procedure> Procedure::FromActuals(const ProcedureDesignator &proc,
13411397
return callee;
13421398
}
13431399

1344-
bool Procedure::CanBeCalledViaImplicitInterface() const {
1345-
// TODO: Pass back information on why we return false
1346-
if (attrs.test(Attr::Elemental) || attrs.test(Attr::BindC)) {
1400+
bool Procedure::CanBeCalledViaImplicitInterface(std::string *whyNot) const {
1401+
if (attrs.test(Attr::Elemental)) {
1402+
if (whyNot) {
1403+
*whyNot = "the procedure is elemental";
1404+
}
1405+
return false; // 15.4.2.2(5,6)
1406+
} else if (attrs.test(Attr::BindC)) {
1407+
if (whyNot) {
1408+
*whyNot = "the procedure is BIND(C)";
1409+
}
13471410
return false; // 15.4.2.2(5,6)
13481411
} else if (cudaSubprogramAttrs &&
13491412
*cudaSubprogramAttrs != common::CUDASubprogramAttrs::Host &&
13501413
*cudaSubprogramAttrs != common::CUDASubprogramAttrs::Global) {
1414+
if (whyNot) {
1415+
*whyNot = "the procedure is CUDA but neither HOST nor GLOBAL";
1416+
}
13511417
return false;
13521418
} else if (IsFunction() &&
1353-
!functionResult->CanBeReturnedViaImplicitInterface()) {
1419+
!functionResult->CanBeReturnedViaImplicitInterface(whyNot)) {
13541420
return false;
13551421
} else {
13561422
for (const DummyArgument &arg : dummyArguments) {
1357-
if (!arg.CanBePassedViaImplicitInterface()) {
1423+
if (!arg.CanBePassedViaImplicitInterface(whyNot)) {
13581424
return false;
13591425
}
13601426
}

flang/lib/Semantics/expression.cpp

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3088,21 +3088,18 @@ const Assignment *ExpressionAnalyzer::Analyze(
30883088
}
30893089

30903090
static bool IsExternalCalledImplicitly(
3091-
parser::CharBlock callSite, const ProcedureDesignator &proc) {
3092-
if (const auto *symbol{proc.GetSymbol()}) {
3093-
return symbol->has<semantics::SubprogramDetails>() &&
3094-
symbol->owner().IsGlobal() &&
3095-
(!symbol->scope() /*ENTRY*/ ||
3096-
!symbol->scope()->sourceRange().Contains(callSite));
3097-
} else {
3098-
return false;
3099-
}
3091+
parser::CharBlock callSite, const Symbol *symbol) {
3092+
return symbol && symbol->owner().IsGlobal() &&
3093+
symbol->has<semantics::SubprogramDetails>() &&
3094+
(!symbol->scope() /*ENTRY*/ ||
3095+
!symbol->scope()->sourceRange().Contains(callSite));
31003096
}
31013097

31023098
std::optional<characteristics::Procedure> ExpressionAnalyzer::CheckCall(
31033099
parser::CharBlock callSite, const ProcedureDesignator &proc,
31043100
ActualArguments &arguments) {
3105-
bool treatExternalAsImplicit{IsExternalCalledImplicitly(callSite, proc)};
3101+
bool treatExternalAsImplicit{
3102+
IsExternalCalledImplicitly(callSite, proc.GetSymbol())};
31063103
const Symbol *procSymbol{proc.GetSymbol()};
31073104
std::optional<characteristics::Procedure> chars;
31083105
if (procSymbol && procSymbol->has<semantics::ProcEntityDetails>() &&
@@ -3138,10 +3135,15 @@ std::optional<characteristics::Procedure> ExpressionAnalyzer::CheckCall(
31383135
}
31393136
bool ok{true};
31403137
if (chars) {
3141-
if (treatExternalAsImplicit && !chars->CanBeCalledViaImplicitInterface()) {
3142-
Say(callSite,
3143-
"References to the procedure '%s' require an explicit interface"_err_en_US,
3144-
DEREF(procSymbol).name());
3138+
std::string whyNot;
3139+
if (treatExternalAsImplicit &&
3140+
!chars->CanBeCalledViaImplicitInterface(&whyNot)) {
3141+
if (auto *msg{Say(callSite,
3142+
"References to the procedure '%s' require an explicit interface"_err_en_US,
3143+
DEREF(procSymbol).name())};
3144+
msg && !whyNot.empty()) {
3145+
msg->Attach(callSite, "%s"_because_en_US, whyNot);
3146+
}
31453147
}
31463148
const SpecificIntrinsic *specificIntrinsic{proc.GetSpecificIntrinsic()};
31473149
bool procIsDummy{procSymbol && IsDummy(*procSymbol)};

flang/lib/Semantics/resolve-names.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7767,14 +7767,26 @@ void ResolveNamesVisitor::HandleProcedureName(
77677767
if (!symbol->attrs().test(Attr::INTRINSIC)) {
77687768
if (CheckImplicitNoneExternal(name.source, *symbol)) {
77697769
MakeExternal(*symbol);
7770+
// Create a place-holder HostAssocDetails symbol to preclude later
7771+
// use of this name as a local symbol; but don't actually use this new
7772+
// HostAssocDetails symbol in expressions.
7773+
MakeHostAssocSymbol(name, *symbol);
7774+
name.symbol = symbol;
77707775
}
77717776
}
77727777
CheckEntryDummyUse(name.source, symbol);
77737778
SetProcFlag(name, *symbol, flag);
77747779
} else if (CheckUseError(name)) {
77757780
// error was reported
77767781
} else {
7777-
symbol = &Resolve(name, symbol)->GetUltimate();
7782+
symbol = &symbol->GetUltimate();
7783+
if (!name.symbol ||
7784+
(name.symbol->has<HostAssocDetails>() && symbol->owner().IsGlobal() &&
7785+
(symbol->has<ProcEntityDetails>() ||
7786+
(symbol->has<SubprogramDetails>() &&
7787+
symbol->scope() /*not ENTRY*/)))) {
7788+
name.symbol = symbol;
7789+
}
77787790
CheckEntryDummyUse(name.source, symbol);
77797791
bool convertedToProcEntity{ConvertToProcEntity(*symbol)};
77807792
if (convertedToProcEntity && !symbol->attrs().test(Attr::EXTERNAL) &&

flang/test/Semantics/call24.f90

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,22 @@ subroutine test()
2727
! descriptor involved, copy-in/copy-out...)
2828

2929
!ERROR: References to the procedure 'foo' require an explicit interface
30+
!BECAUSE: a dummy argument has the allocatable, asynchronous, optional, pointer, target, value, or volatile attribute
3031
call foo(a_pointer)
3132

3233
! This call would be error if the interface was explicit here.
3334

3435
!ERROR: References to the procedure 'foo' require an explicit interface
36+
!BECAUSE: a dummy argument has the allocatable, asynchronous, optional, pointer, target, value, or volatile attribute
3537
call foo(an_array)
3638

3739
!ERROR: References to the procedure 'bar' require an explicit interface
40+
!BECAUSE: a dummy procedure is optional or a pointer
3841
!WARNING: If the procedure's interface were explicit, this reference would be in error
3942
!BECAUSE: Actual argument associated with procedure pointer dummy argument 'a_pointer=' must be a pointer unless INTENT(IN)
4043
call bar(sin)
4144

4245
!ERROR: References to the procedure 'baz' require an explicit interface
46+
!BECAUSE: a dummy procedure is optional or a pointer
4347
call baz(sin)
4448
end subroutine

flang/test/Semantics/call25.f90

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
! RUN: not %flang -fsyntax-only 2>&1 %s | FileCheck %s
1+
! RUN: not %flang -fsyntax-only -pedantic 2>&1 %s | FileCheck %s
22
module m
33
contains
44
subroutine subr1(f)

flang/test/Semantics/local-vs-global.f90

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ program test
7474
call block_data_before_2
7575
call explicit_before_1(1.)
7676
!ERROR: References to the procedure 'explicit_before_2' require an explicit interface
77+
!BECAUSE: a dummy argument has the allocatable, asynchronous, optional, pointer, target, value, or volatile attribute
7778
call explicit_before_2(1.)
7879
!WARNING: If the procedure's interface were explicit, this reference would be in error
7980
!BECAUSE: Dummy argument 'a=' (#1) is not OPTIONAL and is not associated with an actual argument in this procedure reference
@@ -83,6 +84,7 @@ program test
8384
call implicit_before_2
8485
print *, explicit_func_before_1(1.)
8586
!ERROR: References to the procedure 'explicit_func_before_2' require an explicit interface
87+
!BECAUSE: a dummy argument has the allocatable, asynchronous, optional, pointer, target, value, or volatile attribute
8688
print *, explicit_func_before_2(1.)
8789
!WARNING: If the procedure's interface were explicit, this reference would be in error
8890
!BECAUSE: Dummy argument 'a=' (#1) is not OPTIONAL and is not associated with an actual argument in this procedure reference
@@ -96,6 +98,7 @@ program test
9698
call block_data_after_2
9799
call explicit_after_1(1.)
98100
!ERROR: References to the procedure 'explicit_after_2' require an explicit interface
101+
!BECAUSE: a dummy argument has the allocatable, asynchronous, optional, pointer, target, value, or volatile attribute
99102
call explicit_after_2(1.)
100103
!WARNING: If the procedure's interface were explicit, this reference would be in error
101104
!BECAUSE: Dummy argument 'a=' (#1) is not OPTIONAL and is not associated with an actual argument in this procedure reference
@@ -105,6 +108,7 @@ program test
105108
call implicit_after_2
106109
print *, explicit_func_after_1(1.)
107110
!ERROR: References to the procedure 'explicit_func_after_2' require an explicit interface
111+
!BECAUSE: a dummy argument has the allocatable, asynchronous, optional, pointer, target, value, or volatile attribute
108112
print *, explicit_func_after_2(1.)
109113
!WARNING: If the procedure's interface were explicit, this reference would be in error
110114
!BECAUSE: Dummy argument 'a=' (#1) is not OPTIONAL and is not associated with an actual argument in this procedure reference

flang/test/Semantics/reshape.f90

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ program reshaper
5656
!ERROR: Size of 'shape=' argument must not be greater than 15
5757
CALL ext_sub(RESHAPE([(n, n=1,20)], &
5858
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]))
59-
!WARNING: Reference to the procedure 'ext_sub' has an implicit interface that is distinct from another reference: incompatible dummy argument #1: incompatible dummy data object shapes
59+
!ERROR: Reference to the procedure 'ext_sub' has an implicit interface that is distinct from another reference: incompatible dummy argument #1: incompatible dummy data object shapes
6060
!ERROR: 'shape=' argument must not have a negative extent
6161
CALL ext_sub(RESHAPE([(n, n=1,20)], [1, -5, 3]))
6262
!ERROR: 'order=' argument has unacceptable rank 2

0 commit comments

Comments
 (0)