[flang] Refactor "unused"/"used without definition" warnings (#179539)

I'm emitting a false warning "used without definition" warning for
variable cited in an inquiry intrinsic (e.g. LBOUND), and failing to
emit an "unused local" warning for allocatables that only appear in
ALLOCATE statements.

This patch makes the analysis more context-aware by rewriting it in
terms of typed expression traversal, and moves the analysis part into
Evaluate/check-expression.cpp from Semantics/expression.cpp.
This commit is contained in:
Peter Klausler 2026-02-06 09:29:50 -08:00 committed by GitHub
parent 621fe6783f
commit 9d0043a35f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 174 additions and 114 deletions

View file

@ -180,5 +180,14 @@ std::optional<parser::Message> CheckStatementFunction(
std::optional<bool> ActualArgNeedsCopy(const ActualArgument *,
const characteristics::DummyArgument *, FoldingContext &, bool forCopyOut);
// Scan expressions and note uses of values of symbols.
semantics::UnorderedSymbolSet CollectUsedSymbolValues(
semantics::SemanticsContext &, const Expr<SomeType> &,
bool isDefinition = false);
semantics::UnorderedSymbolSet CollectUsedSymbolValues(
semantics::SemanticsContext &, const ProcedureRef &);
semantics::UnorderedSymbolSet CollectUsedSymbolValues(
semantics::SemanticsContext &, const Assignment &);
} // namespace Fortran::evaluate
#endif

View file

@ -1638,6 +1638,9 @@ std::optional<int> GetDummyArgumentNumber(const Symbol *);
const Symbol *FindAncestorModuleProcedure(const Symbol *symInSubmodule);
// Given a Cray pointee symbol, returns the related Cray pointer symbol.
const Symbol &GetCrayPointer(const Symbol &crayPointee);
} // namespace Fortran::semantics
#endif // FORTRAN_EVALUATE_TOOLS_H_

View file

@ -465,12 +465,8 @@ evaluate::Expr<evaluate::SubscriptInteger> AnalyzeKindSelector(
SemanticsContext &, common::TypeCategory,
const std::optional<parser::KindSelector> &);
void NoteUsedSymbols(SemanticsContext &, const SomeExpr &);
void NoteUsedSymbols(SemanticsContext &, const evaluate::ProcedureRef &);
void NoteUsedSymbols(SemanticsContext &, const evaluate::Assignment &);
void NoteUsedSymbols(SemanticsContext &, const parser::TypedExpr &);
void NoteUsedSymbols(SemanticsContext &, const parser::TypedCall &);
void NoteUsedSymbols(SemanticsContext &, const parser::TypedAssignment &);
void NoteUsedSymbols(
SemanticsContext &, const SomeExpr &, bool isDefinition = false);
// Semantic analysis of all expressions in a parse tree, which becomes
// decorated with typed representations for top-level expressions.
@ -499,11 +495,11 @@ public:
return false;
}
bool Pre(const parser::AllocateObject &x) {
AnalyzeAndNoteUses(x);
AnalyzeAndNoteUses(x, /*isDefinition=*/true);
return false;
}
bool Pre(const parser::PointerObject &x) {
AnalyzeAndNoteUses(x);
AnalyzeAndNoteUses(x, /*isDefinition=*/true);
return false;
}
bool Pre(const parser::DataStmtObject &);
@ -583,14 +579,23 @@ public:
}
private:
template <typename A> void AnalyzeAndNoteUses(const A &x) {
template <typename A>
void AnalyzeAndNoteUses(const A &x, bool isDefinition = false) {
exprAnalyzer_.Analyze(x);
if constexpr (parser::HasTypedExpr<A>::value) {
NoteUsedSymbols(context_, x.typedExpr);
if (x.typedExpr && x.typedExpr->v) {
NoteUsedSymbols(context_, *x.typedExpr->v, isDefinition);
}
} else if constexpr (parser::HasTypedCall<A>::value) {
NoteUsedSymbols(context_, x.typedCall);
if (x.typedCall) {
context_.NoteUsedSymbols(
evaluate::CollectUsedSymbolValues(context_, *x.typedCall));
}
} else if constexpr (parser::HasTypedAssignment<A>::value) {
NoteUsedSymbols(context_, x.typedAssignment);
if (x.typedAssignment && x.typedAssignment->v) {
context_.NoteUsedSymbols(
evaluate::CollectUsedSymbolValues(context_, *x.typedAssignment->v));
}
}
}
bool InWhereBody() const { return whereDepth_ > 0; }

View file

@ -332,6 +332,7 @@ public:
void NoteDefinedSymbol(const Symbol &);
bool IsSymbolDefined(const Symbol &) const;
void NoteUsedSymbol(const Symbol &);
void NoteUsedSymbols(const UnorderedSymbolSet &);
bool IsSymbolUsed(const Symbol &) const;
void DumpSymbols(llvm::raw_ostream &);

View file

@ -334,9 +334,6 @@ const Symbol *FindExternallyVisibleObject(
// specific procedure of the same name, return it instead.
const Symbol &BypassGeneric(const Symbol &);
// Given a cray pointee symbol, returns the related cray pointer symbol.
const Symbol &GetCrayPointer(const Symbol &crayPointee);
using SomeExpr = evaluate::Expr<evaluate::SomeType>;
bool ExprHasTypeCategory(

View file

@ -1665,4 +1665,121 @@ std::optional<bool> ActualArgNeedsCopy(const ActualArgument *actual,
return std::nullopt;
}
// CollectUsedSymbolValues()
class CollectUsedSymbolValuesHelper
: public SetTraverse<CollectUsedSymbolValuesHelper,
semantics::UnorderedSymbolSet> {
public:
using Result = semantics::UnorderedSymbolSet;
using Base = SetTraverse<CollectUsedSymbolValuesHelper, Result>;
explicit CollectUsedSymbolValuesHelper(
semantics::SemanticsContext &c, bool isDefinition = false)
: Base{*this}, context_{c}, isDefinition_{isDefinition} {}
using Base::operator();
Result operator()(const semantics::Symbol &symbol) const {
Result result;
if (!isDefinition_) {
const Symbol &root{semantics::GetAssociationRoot(symbol)};
switch (root.owner().kind()) {
case semantics::Scope::Kind::Subprogram:
case semantics::Scope::Kind::MainProgram:
case semantics::Scope::Kind::BlockConstruct:
if ((root.has<semantics::ObjectEntityDetails>() ||
IsProcedurePointer(root))) {
result.insert(root);
if (root.test(semantics::Symbol::Flag::CrayPointee)) {
result.insert(semantics::GetCrayPointer(root));
}
}
break;
default:
break;
}
}
return result;
}
Result operator()(const Subscript &subscript) {
auto restorer{common::ScopedSet(isDefinition_, false)};
return (*this)(subscript.u);
}
template <typename T> Result operator()(const FunctionRef<T> &fRef) {
return (*this)(static_cast<ProcedureRef>(fRef));
}
Result operator()(const ProcedureRef &call) {
auto restorer{common::ScopedSet(isDefinition_, false)};
Result result{(*this)(call.proc())};
int skipLeading{0};
if (const auto *intrinsic{call.proc().GetSpecificIntrinsic()}) {
if (context_.intrinsics().GetIntrinsicClass(intrinsic->name) ==
IntrinsicClass::inquiryFunction) {
skipLeading = 1; // first argument to inquiry doesn't count as a use
}
}
for (const auto &maybeArg : call.arguments()) {
if (skipLeading) {
--skipLeading;
} else if (maybeArg) {
if (const auto *expr{maybeArg->UnwrapExpr()}) {
if (IsBindingUsedAsProcedure(*expr)) {
// Ignore procedure bindings being used as actual procedures
// (a local extension).
} else {
result = Combine(std::move(result), (*this)(*expr));
}
}
}
}
return result;
}
Result operator()(const Assignment &assignment) {
auto restorer{common::ScopedSet(isDefinition_, true)};
Result result{(*this)(assignment.lhs)};
if (IsBindingUsedAsProcedure(assignment.rhs)) {
// Don't look at the RHS, we're just using its binding (extension).
} else {
auto restorer{common::ScopedSet(isDefinition_, false)};
result = Combine(std::move(result), (*this)(assignment.rhs));
}
return result;
}
Result operator()(const TypeParamInquiry &) const {
return {}; // doesn't count as a use
}
Result operator()(const DescriptorInquiry &) const {
return {}; // doesn't count as a use
}
private:
static bool IsBindingUsedAsProcedure(const Expr<SomeType> &expr) {
if (const auto *pd{std::get_if<ProcedureDesignator>(&expr.u)}) {
if (const Symbol *symbol{pd->GetSymbol()}) {
return symbol->has<semantics::ProcBindingDetails>();
}
}
return false;
}
semantics::SemanticsContext &context_;
bool isDefinition_{false};
};
semantics::UnorderedSymbolSet CollectUsedSymbolValues(
semantics::SemanticsContext &context, const Expr<SomeType> &expr,
bool isDefinition) {
return CollectUsedSymbolValuesHelper{context, isDefinition}(expr);
}
semantics::UnorderedSymbolSet CollectUsedSymbolValues(
semantics::SemanticsContext &context, const ProcedureRef &call) {
return CollectUsedSymbolValuesHelper{context}(call);
}
semantics::UnorderedSymbolSet CollectUsedSymbolValues(
semantics::SemanticsContext &context, const Assignment &assignment) {
return CollectUsedSymbolValuesHelper{context}(assignment);
}
} // namespace Fortran::evaluate

View file

@ -2645,4 +2645,16 @@ const Symbol *FindAncestorModuleProcedure(const Symbol *symInSubmodule) {
return nullptr;
}
const Symbol &GetCrayPointer(const Symbol &crayPointee) {
const Symbol *found{nullptr};
const Symbol &ultimate{crayPointee.GetUltimate()};
for (const auto &[pointee, pointer] : ultimate.owner().crayPointers()) {
if (pointee == ultimate.name()) {
found = &pointer.get();
break;
}
}
return DEREF(found);
}
} // namespace Fortran::semantics

View file

@ -9,6 +9,7 @@
#include "check-coarray.h"
#include "definable.h"
#include "flang/Common/indirection.h"
#include "flang/Evaluate/check-expression.h"
#include "flang/Evaluate/expression.h"
#include "flang/Parser/message.h"
#include "flang/Parser/parse-tree.h"

View file

@ -5321,94 +5321,10 @@ evaluate::Expr<evaluate::SubscriptInteger> AnalyzeKindSelector(
return analyzer.AnalyzeKindSelector(category, selector);
}
// NoteUsedSymbols()
static void NoteUsedSymbol(SemanticsContext &context, const Symbol &symbol) {
const Symbol &root{GetAssociationRoot(symbol)};
switch (root.owner().kind()) {
case semantics::Scope::Kind::Subprogram:
case semantics::Scope::Kind::MainProgram:
case semantics::Scope::Kind::BlockConstruct:
if ((root.has<semantics::ObjectEntityDetails>() ||
IsProcedurePointer(root))) {
context.NoteUsedSymbol(root);
if (root.test(Symbol::Flag::CrayPointee)) {
context.NoteUsedSymbol(GetCrayPointer(root));
}
}
break;
default:
break;
}
}
template <typename A>
void NoteUsedSymbolsHelper(SemanticsContext &context, const A &x) {
if (context.ShouldWarn(common::UsageWarning::UnusedVariable)) {
for (const Symbol &symbol : CollectSymbols(x)) {
NoteUsedSymbol(context, symbol);
}
}
}
void NoteUsedSymbols(SemanticsContext &context, const SomeExpr &expr) {
NoteUsedSymbolsHelper(context, expr);
}
static bool IsBindingUsedAsProcedure(const SomeExpr &expr) {
if (const auto *pd{std::get_if<evaluate::ProcedureDesignator>(&expr.u)}) {
if (const Symbol *symbol{pd->GetSymbol()}) {
return symbol->has<ProcBindingDetails>();
}
}
return false;
}
void NoteUsedSymbols(
SemanticsContext &context, const evaluate::ProcedureRef &call) {
NoteUsedSymbolsHelper(context, call.proc());
for (const auto &maybeArg : call.arguments()) {
if (maybeArg) {
if (const auto *expr{maybeArg->UnwrapExpr()}) {
if (!IsBindingUsedAsProcedure(*expr)) {
// Ignore procedure bindings being used as actual procedures
// (a local extension).
NoteUsedSymbolsHelper(context, *expr);
}
}
}
}
}
void NoteUsedSymbols(
SemanticsContext &context, const evaluate::Assignment &assignment) {
if (IsBindingUsedAsProcedure(assignment.rhs)) {
// Don't look at the RHS, we're just using its binding (extension).
NoteUsedSymbolsHelper(context, assignment.lhs);
} else {
NoteUsedSymbolsHelper(context, assignment);
}
}
void NoteUsedSymbols(
SemanticsContext &context, const parser::TypedExpr &typedExpr) {
if (typedExpr && typedExpr->v) {
NoteUsedSymbols(context, *typedExpr->v);
}
}
void NoteUsedSymbols(
SemanticsContext &context, const parser::TypedCall &typedCall) {
if (typedCall) {
NoteUsedSymbols(context, *typedCall);
}
}
void NoteUsedSymbols(
SemanticsContext &context, const parser::TypedAssignment &typedAssignment) {
if (typedAssignment && typedAssignment->v) {
NoteUsedSymbols(context, *typedAssignment->v);
}
SemanticsContext &context, const SomeExpr &expr, bool isDefinition) {
context.NoteUsedSymbols(
evaluate::CollectUsedSymbolValues(context, expr, isDefinition));
}
ExprChecker::ExprChecker(SemanticsContext &context) : context_{context} {}

View file

@ -819,6 +819,11 @@ bool SemanticsContext::IsSymbolDefined(const Symbol &symbol) const {
void SemanticsContext::NoteUsedSymbol(const Symbol &symbol) {
isUsed_.insert(symbol);
}
void SemanticsContext::NoteUsedSymbols(const UnorderedSymbolSet &set) {
for (const Symbol &symbol : set) {
NoteUsedSymbol(symbol);
}
}
bool SemanticsContext::IsSymbolUsed(const Symbol &symbol) const {
return isUsed_.find(symbol) != isUsed_.end();

View file

@ -346,18 +346,6 @@ const Symbol &BypassGeneric(const Symbol &symbol) {
return symbol;
}
const Symbol &GetCrayPointer(const Symbol &crayPointee) {
const Symbol *found{nullptr};
const Symbol &ultimate{crayPointee.GetUltimate()};
for (const auto &[pointee, pointer] : ultimate.owner().crayPointers()) {
if (pointee == ultimate.name()) {
found = &pointer.get();
break;
}
}
return DEREF(found);
}
bool ExprHasTypeCategory(
const SomeExpr &expr, const common::TypeCategory &type) {
auto dynamicType{expr.GetType()};

View file

@ -0,0 +1,6 @@
!RUN: %python %S/test_errors.py %s %flang_fc1 -pedantic -Werror
!WARNING: Value of local variable 'x' is never used [-Wunused-variable]
real, allocatable:: x(:)
allocate(x(1))
print *, lbound(x)
end