[flang-commits] [flang] [flang] Refactor "unused"/"used without definition" warnings (PR #179539)
via flang-commits
flang-commits at lists.llvm.org
Tue Feb 3 11:57:33 PST 2026
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-flang-semantics
Author: Peter Klausler (klausler)
<details>
<summary>Changes</summary>
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.
---
Full diff: https://github.com/llvm/llvm-project/pull/179539.diff
12 Files Affected:
- (modified) flang/include/flang/Evaluate/check-expression.h (+9)
- (modified) flang/include/flang/Evaluate/tools.h (+3)
- (modified) flang/include/flang/Semantics/expression.h (+17-12)
- (modified) flang/include/flang/Semantics/semantics.h (+1)
- (modified) flang/include/flang/Semantics/tools.h (-3)
- (modified) flang/lib/Evaluate/check-expression.cpp (+117)
- (modified) flang/lib/Evaluate/tools.cpp (+12)
- (modified) flang/lib/Semantics/check-coarray.cpp (+1)
- (modified) flang/lib/Semantics/expression.cpp (+3-87)
- (modified) flang/lib/Semantics/semantics.cpp (+5)
- (modified) flang/lib/Semantics/tools.cpp (-12)
- (added) flang/test/Semantics/bug2174.f90 (+6)
``````````diff
diff --git a/flang/include/flang/Evaluate/check-expression.h b/flang/include/flang/Evaluate/check-expression.h
index 41a98a4bea6a2..c837a7d72e8b1 100644
--- a/flang/include/flang/Evaluate/check-expression.h
+++ b/flang/include/flang/Evaluate/check-expression.h
@@ -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
diff --git a/flang/include/flang/Evaluate/tools.h b/flang/include/flang/Evaluate/tools.h
index 6a424f2cbb04e..0776a0afb4d92 100644
--- a/flang/include/flang/Evaluate/tools.h
+++ b/flang/include/flang/Evaluate/tools.h
@@ -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_
diff --git a/flang/include/flang/Semantics/expression.h b/flang/include/flang/Semantics/expression.h
index 50f75b2304d95..4de20f5ea46a5 100644
--- a/flang/include/flang/Semantics/expression.h
+++ b/flang/include/flang/Semantics/expression.h
@@ -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 @@ class ExprChecker {
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 @@ class ExprChecker {
}
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; }
diff --git a/flang/include/flang/Semantics/semantics.h b/flang/include/flang/Semantics/semantics.h
index 88bc23380b22d..f7a8e76879b77 100644
--- a/flang/include/flang/Semantics/semantics.h
+++ b/flang/include/flang/Semantics/semantics.h
@@ -332,6 +332,7 @@ class SemanticsContext {
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 &);
diff --git a/flang/include/flang/Semantics/tools.h b/flang/include/flang/Semantics/tools.h
index 1c3477013b559..5773e93801ed4 100644
--- a/flang/include/flang/Semantics/tools.h
+++ b/flang/include/flang/Semantics/tools.h
@@ -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(
diff --git a/flang/lib/Evaluate/check-expression.cpp b/flang/lib/Evaluate/check-expression.cpp
index 9e63f216693a2..f18953c1b9849 100644
--- a/flang/lib/Evaluate/check-expression.cpp
+++ b/flang/lib/Evaluate/check-expression.cpp
@@ -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
diff --git a/flang/lib/Evaluate/tools.cpp b/flang/lib/Evaluate/tools.cpp
index 37f718c98ebb5..9b7d4c758769e 100644
--- a/flang/lib/Evaluate/tools.cpp
+++ b/flang/lib/Evaluate/tools.cpp
@@ -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
diff --git a/flang/lib/Semantics/check-coarray.cpp b/flang/lib/Semantics/check-coarray.cpp
index 74e2d4e25c7ec..00b97162ff0d4 100644
--- a/flang/lib/Semantics/check-coarray.cpp
+++ b/flang/lib/Semantics/check-coarray.cpp
@@ -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"
diff --git a/flang/lib/Semantics/expression.cpp b/flang/lib/Semantics/expression.cpp
index fef6b4f0470dd..7c3db734bb612 100644
--- a/flang/lib/Semantics/expression.cpp
+++ b/flang/lib/Semantics/expression.cpp
@@ -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} {}
diff --git a/flang/lib/Semantics/semantics.cpp b/flang/lib/Semantics/semantics.cpp
index 1f23a6c29bbe7..72b36416adda2 100644
--- a/flang/lib/Semantics/semantics.cpp
+++ b/flang/lib/Semantics/semantics.cpp
@@ -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();
diff --git a/flang/lib/Semantics/tools.cpp b/flang/lib/Semantics/tools.cpp
index baa8e6d7f59a3..7fe44317a198f 100644
--- a/flang/lib/Semantics/tools.cpp
+++ b/flang/lib/Semantics/tools.cpp
@@ -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()};
diff --git a/flang/test/Semantics/bug2174.f90 b/flang/test/Semantics/bug2174.f90
new file mode 100644
index 0000000000000..fcea9761ccad2
--- /dev/null
+++ b/flang/test/Semantics/bug2174.f90
@@ -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
``````````
</details>
https://github.com/llvm/llvm-project/pull/179539
More information about the flang-commits
mailing list