Skip to content

Commit 33c27f2

Browse files
authored
[flang] Warn about undefined function results (#99533)
When the result of a function never appears in a variable definition context, emit a warning. If the function has multiple result variables due to alternate ENTRY statements, any definition will suffice. The implementation of this check is tied to the general variable definability checking utility in semantics. Every variable definition context uses it to ensure that no undefinable variable is being defined. A set of defined variables is maintained in the SemanticsContext and, when the warning is enabled and no fatal error has been reported, the scope tree is traversed and all the function subprograms' results are tested for membership in that set.
1 parent 971a1ac commit 33c27f2

File tree

17 files changed

+254
-20
lines changed

17 files changed

+254
-20
lines changed

flang/include/flang/Common/Fortran-features.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ ENUM_CLASS(UsageWarning, Portability, PointerToUndefinable,
7070
IgnoredIntrinsicFunctionType, PreviousScalarUse,
7171
RedeclaredInaccessibleComponent, ImplicitShared, IndexVarRedefinition,
7272
IncompatibleImplicitInterfaces, BadTypeForTarget,
73-
VectorSubscriptFinalization)
73+
VectorSubscriptFinalization, UndefinedFunctionResult)
7474

7575
using LanguageFeatures = EnumSet<LanguageFeature, LanguageFeature_enumSize>;
7676
using UsageWarnings = EnumSet<UsageWarning, UsageWarning_enumSize>;
@@ -144,6 +144,7 @@ class LanguageFeatureControl {
144144
warnUsage_.set(UsageWarning::IncompatibleImplicitInterfaces);
145145
warnUsage_.set(UsageWarning::BadTypeForTarget);
146146
warnUsage_.set(UsageWarning::VectorSubscriptFinalization);
147+
warnUsage_.set(UsageWarning::UndefinedFunctionResult);
147148
}
148149
LanguageFeatureControl(const LanguageFeatureControl &) = default;
149150

flang/include/flang/Semantics/semantics.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,9 @@ class SemanticsContext {
254254
// behavior.
255255
CommonBlockList GetCommonBlocks() const;
256256

257+
void NoteDefinedSymbol(const Symbol &);
258+
bool IsSymbolDefined(const Symbol &) const;
259+
257260
private:
258261
struct ScopeIndexComparator {
259262
bool operator()(parser::CharBlock, parser::CharBlock) const;
@@ -303,6 +306,7 @@ class SemanticsContext {
303306
std::unique_ptr<CommonBlockMap> commonBlockMap_;
304307
ModuleDependences moduleDependences_;
305308
std::map<const Symbol *, SourceName> moduleFileOutputRenamings_;
309+
UnorderedSymbolSet isDefined_;
306310
};
307311

308312
class Semantics {

flang/include/flang/Semantics/tools.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ const Symbol *FindPointerComponent(const DeclTypeSpec &);
5252
const Symbol *FindPointerComponent(const Symbol &);
5353
const Symbol *FindInterface(const Symbol &);
5454
const Symbol *FindSubprogram(const Symbol &);
55-
const Symbol *FindFunctionResult(const Symbol &);
5655
const Symbol *FindOverriddenBinding(
5756
const Symbol &, bool &isInaccessibleDeferred);
5857
const Symbol *FindGlobal(const Symbol &);

flang/lib/Semantics/check-allocate.cpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -600,10 +600,13 @@ bool AllocationCheckerHelper::RunChecks(SemanticsContext &context) {
600600
const Scope &subpScope{
601601
GetProgramUnitContaining(context.FindScope(name_.source))};
602602
if (allocateObject_.typedExpr && allocateObject_.typedExpr->v) {
603-
if (auto whyNot{WhyNotDefinable(name_.source, subpScope,
604-
{DefinabilityFlag::PointerDefinition,
605-
DefinabilityFlag::AcceptAllocatable},
606-
*allocateObject_.typedExpr->v)}) {
603+
DefinabilityFlags flags{DefinabilityFlag::PointerDefinition,
604+
DefinabilityFlag::AcceptAllocatable};
605+
if (allocateInfo_.gotSource) {
606+
flags.set(DefinabilityFlag::SourcedAllocation);
607+
}
608+
if (auto whyNot{WhyNotDefinable(
609+
name_.source, subpScope, flags, *allocateObject_.typedExpr->v)}) {
607610
context
608611
.Say(name_.source,
609612
"Name in ALLOCATE statement is not definable"_err_en_US)

flang/lib/Semantics/check-call.cpp

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ namespace characteristics = Fortran::evaluate::characteristics;
2727
namespace Fortran::semantics {
2828

2929
static void CheckImplicitInterfaceArg(evaluate::ActualArgument &arg,
30-
parser::ContextualMessages &messages, evaluate::FoldingContext &context) {
30+
parser::ContextualMessages &messages, SemanticsContext &context) {
3131
auto restorer{
3232
messages.SetLocation(arg.sourceLocation().value_or(messages.at()))};
3333
if (auto kw{arg.keyword()}) {
@@ -79,8 +79,12 @@ static void CheckImplicitInterfaceArg(evaluate::ActualArgument &arg,
7979
messages.Say(
8080
"VOLATILE argument requires an explicit interface"_err_en_US);
8181
}
82+
if (const Symbol & base{named->GetFirstSymbol()};
83+
IsFunctionResult(base)) {
84+
context.NoteDefinedSymbol(base);
85+
}
8286
} else if (auto argChars{characteristics::DummyArgument::FromActual(
83-
"actual argument", *expr, context,
87+
"actual argument", *expr, context.foldingContext(),
8488
/*forImplicitInterface=*/true)}) {
8589
const auto *argProcDesignator{
8690
std::get_if<evaluate::ProcedureDesignator>(&expr->u)};
@@ -647,8 +651,8 @@ static void CheckExplicitDataArg(const characteristics::DummyDataObject &dummy,
647651
actualLastSymbol->name(), dummyName);
648652
}
649653

650-
// Definability
651-
bool actualIsVariable{evaluate::IsVariable(actual)};
654+
// Definability checking
655+
// Problems with polymorphism are caught in the callee's definition.
652656
if (scope) {
653657
std::optional<parser::MessageFixedText> undefinableMessage;
654658
if (dummy.intent == common::Intent::Out) {
@@ -670,7 +674,6 @@ static void CheckExplicitDataArg(const characteristics::DummyDataObject &dummy,
670674
}
671675
}
672676
if (undefinableMessage) {
673-
// Problems with polymorphism are caught in the callee's definition.
674677
DefinabilityFlags flags{DefinabilityFlag::PolymorphicOkInPure};
675678
if (isElemental) { // 15.5.2.4(21)
676679
flags.set(DefinabilityFlag::VectorSubscriptIsOk);
@@ -689,6 +692,14 @@ static void CheckExplicitDataArg(const characteristics::DummyDataObject &dummy,
689692
messages.Say(std::move(*whyNot));
690693
}
691694
}
695+
} else if (dummy.intent != common::Intent::In ||
696+
(dummyIsPointer && !actualIsPointer)) {
697+
if (auto named{evaluate::ExtractNamedEntity(actual)}) {
698+
if (const Symbol & base{named->GetFirstSymbol()};
699+
IsFunctionResult(base)) {
700+
context.NoteDefinedSymbol(base);
701+
}
702+
}
692703
}
693704
}
694705

@@ -893,6 +904,7 @@ static void CheckExplicitDataArg(const characteristics::DummyDataObject &dummy,
893904
// argument
894905
if (dummy.attrs.test(characteristics::DummyDataObject::Attr::Target) &&
895906
context.ShouldWarn(common::UsageWarning::NonTargetPassedToTarget)) {
907+
bool actualIsVariable{evaluate::IsVariable(actual)};
896908
bool actualIsTemp{!actualIsVariable || HasVectorSubscript(actual) ||
897909
evaluate::ExtractCoarrayRef(actual)};
898910
if (actualIsTemp) {
@@ -1416,7 +1428,8 @@ static void CheckAssociated(evaluate::ActualArguments &arguments,
14161428
if (auto whyNot{WhyNotDefinable(
14171429
pointerArg->sourceLocation().value_or(messages.at()),
14181430
*scope,
1419-
DefinabilityFlags{DefinabilityFlag::PointerDefinition},
1431+
DefinabilityFlags{DefinabilityFlag::PointerDefinition,
1432+
DefinabilityFlag::DoNotNoteDefinition},
14201433
*pointerExpr)}) {
14211434
if (whyNot->IsFatal()) {
14221435
if (auto *msg{messages.Say(pointerArg->sourceLocation(),
@@ -2021,7 +2034,7 @@ bool CheckArguments(const characteristics::Procedure &proc,
20212034
auto restorer{messages.SetMessages(buffer)};
20222035
for (auto &actual : actuals) {
20232036
if (actual) {
2024-
CheckImplicitInterfaceArg(*actual, messages, foldingContext);
2037+
CheckImplicitInterfaceArg(*actual, messages, context);
20252038
}
20262039
}
20272040
}

flang/lib/Semantics/check-purity.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ void PurityChecker::Enter(const parser::FunctionSubprogram &func) {
3131
stmt.source, std::get<std::list<parser::PrefixSpec>>(stmt.statement.t));
3232
}
3333

34-
void PurityChecker::Leave(const parser::FunctionSubprogram &) { Left(); }
34+
void PurityChecker::Leave(const parser::FunctionSubprogram &func) { Left(); }
3535

3636
bool PurityChecker::InPureSubprogram() const {
3737
return pureDepth_ >= 0 && depth_ >= pureDepth_;

flang/lib/Semantics/definable.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,12 @@ static std::optional<parser::Message> WhyNotDefinableBase(parser::CharBlock at,
127127
(!IsPointer(ultimate) || (isWholeSymbol && isPointerDefinition))) {
128128
return BlameSymbol(
129129
at, "'%s' is an INTENT(IN) dummy argument"_en_US, original);
130+
} else if (acceptAllocatable &&
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);
130136
}
131137
if (const Scope * pure{FindPureProcedureContaining(scope)}) {
132138
// Additional checking for pure subprograms.

flang/lib/Semantics/definable.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ ENUM_CLASS(DefinabilityFlag,
3030
DuplicatesAreOk, // vector subscript may have duplicates
3131
PointerDefinition, // a pointer is being defined, not its target
3232
AcceptAllocatable, // treat allocatable as if it were a pointer
33-
PolymorphicOkInPure) // don't check for polymorphic type in pure subprogram
33+
SourcedAllocation, // ALLOCATE(a,SOURCE=)
34+
PolymorphicOkInPure, // don't check for polymorphic type in pure subprogram
35+
DoNotNoteDefinition) // context does not imply definition
3436

3537
using DefinabilityFlags =
3638
common::EnumSet<DefinabilityFlag, DefinabilityFlag_enumSize>;

flang/lib/Semantics/pointer-assignment.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,8 +358,10 @@ bool PointerAssignmentChecker::Check(const evaluate::Designator<T> &d) {
358358
Say(std::get<MessageFormattedText>(*msg));
359359
}
360360
return false;
361+
} else {
362+
context_.NoteDefinedSymbol(*base);
363+
return true;
361364
}
362-
return true;
363365
}
364366

365367
// Common handling for procedure pointer right-hand sides

flang/lib/Semantics/semantics.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,41 @@ class MiscChecker : public virtual BaseChecker {
160160
SemanticsContext &context_;
161161
};
162162

163+
static void WarnUndefinedFunctionResult(
164+
SemanticsContext &context, const Scope &scope) {
165+
auto WasDefined{[&context](const Symbol &symbol) {
166+
return context.IsSymbolDefined(symbol) ||
167+
IsInitialized(symbol, /*ignoreDataStatements=*/true,
168+
/*ignoreAllocatable=*/true, /*ignorePointer=*/true);
169+
}};
170+
if (const Symbol * symbol{scope.symbol()}) {
171+
if (const auto *subp{symbol->detailsIf<SubprogramDetails>()}) {
172+
if (subp->isFunction() && !subp->isInterface() && !subp->stmtFunction()) {
173+
bool wasDefined{WasDefined(subp->result())};
174+
if (!wasDefined) {
175+
// Definitions of ENTRY result variables also count.
176+
for (const auto &pair : scope) {
177+
const Symbol &local{*pair.second};
178+
if (IsFunctionResult(local) && WasDefined(local)) {
179+
wasDefined = true;
180+
break;
181+
}
182+
}
183+
if (!wasDefined) {
184+
context.Say(
185+
symbol->name(), "Function result is never defined"_warn_en_US);
186+
}
187+
}
188+
}
189+
}
190+
}
191+
if (!scope.IsModuleFile()) {
192+
for (const Scope &child : scope.children()) {
193+
WarnUndefinedFunctionResult(context, child);
194+
}
195+
}
196+
}
197+
163198
using StatementSemanticsPass1 = ExprChecker;
164199
using StatementSemanticsPass2 = SemanticsVisitor<AllocateChecker,
165200
ArithmeticIfStmtChecker, AssignmentChecker, CaseChecker, CoarrayChecker,
@@ -187,6 +222,9 @@ static bool PerformStatementSemantics(
187222
SemanticsVisitor<CUDAChecker>{context}.Walk(program);
188223
}
189224
if (!context.AnyFatalError()) {
225+
if (context.ShouldWarn(common::UsageWarning::UndefinedFunctionResult)) {
226+
WarnUndefinedFunctionResult(context, context.globalScope());
227+
}
190228
pass2.CompileDataInitializationsIntoInitializers();
191229
}
192230
return !context.AnyFatalError();
@@ -712,4 +750,12 @@ CommonBlockList SemanticsContext::GetCommonBlocks() const {
712750
return {};
713751
}
714752

753+
void SemanticsContext::NoteDefinedSymbol(const Symbol &symbol) {
754+
isDefined_.insert(symbol);
755+
}
756+
757+
bool SemanticsContext::IsSymbolDefined(const Symbol &symbol) const {
758+
return isDefined_.find(symbol) != isDefined_.end();
759+
}
760+
715761
} // namespace Fortran::semantics

0 commit comments

Comments
 (0)