[flang-commits] [flang] 5fc77c1 - [flang] Add new warnings for unused & undefined locals (#173504)

via flang-commits flang-commits at lists.llvm.org
Wed Dec 31 12:42:11 PST 2025


Author: Peter Klausler
Date: 2025-12-31T12:42:06-08:00
New Revision: 5fc77c166639afbc980c9ad19a7486af7b7604b8

URL: https://github.com/llvm/llvm-project/commit/5fc77c166639afbc980c9ad19a7486af7b7604b8
DIFF: https://github.com/llvm/llvm-project/commit/5fc77c166639afbc980c9ad19a7486af7b7604b8.diff

LOG: [flang] Add new warnings for unused & undefined locals (#173504)

Add a requested warning for completely unused local variables. The
implementation runs a scan over typed expressions during the existing
expression semantics pass to detect variable uses, and a routine at the
end of semantics to take a pass over the symbol tables to find unused
locals.

The new infrastructure needed to detect variable uses, and the existing
infrastructure that detects potential variable definitions, then makes
it easy to detect variables that are used without any possible
initialization or definition, so I did that too.

The warning for unused locals is off by default -- they might indicate a
misspelling (that IMPLICIT NONE would have caught), but seem otherwise
generally benign. The warning for uses of completely uninitialized and
undefined variables, however, is enabled by default, since that's likely
to indicate a program bug that should be investigated.

This patch touches a lot of files lightly. Many of these files are tests
that would have produced needless warning noise; one new test was added.

Fixes https://github.com/llvm/llvm-project/issues/173276.

Added: 
    flang/test/Semantics/unused.f90

Modified: 
    flang/include/flang/Evaluate/tools.h
    flang/include/flang/Parser/parse-tree.h
    flang/include/flang/Parser/tools.h
    flang/include/flang/Semantics/expression.h
    flang/include/flang/Semantics/semantics.h
    flang/include/flang/Support/Fortran-features.h
    flang/lib/Evaluate/tools.cpp
    flang/lib/Lower/OpenMP/Atomic.cpp
    flang/lib/Semantics/check-allocate.cpp
    flang/lib/Semantics/check-omp-atomic.cpp
    flang/lib/Semantics/definable.cpp
    flang/lib/Semantics/expression.cpp
    flang/lib/Semantics/resolve-names.cpp
    flang/lib/Semantics/semantics.cpp
    flang/lib/Support/Fortran-features.cpp
    flang/test/Driver/disable-diagnostic.f90
    flang/test/Semantics/OpenMP/copying.f90
    flang/test/Semantics/bindings03.f90
    flang/test/Semantics/kinds05b.f90
    flang/test/Semantics/long-name.f90

Removed: 
    


################################################################################
diff  --git a/flang/include/flang/Evaluate/tools.h b/flang/include/flang/Evaluate/tools.h
index 888e4bb89f1b2..ea26a0ee9da7a 100644
--- a/flang/include/flang/Evaluate/tools.h
+++ b/flang/include/flang/Evaluate/tools.h
@@ -1089,6 +1089,10 @@ extern template semantics::UnorderedSymbolSet CollectSymbols(
     const Expr<SomeInteger> &);
 extern template semantics::UnorderedSymbolSet CollectSymbols(
     const Expr<SubscriptInteger> &);
+extern template semantics::UnorderedSymbolSet CollectSymbols(
+    const ProcedureDesignator &);
+extern template semantics::UnorderedSymbolSet CollectSymbols(
+    const Assignment &);
 
 // Collects Symbols of interest for the CUDA data transfer in an expression
 template <typename A>
@@ -1341,8 +1345,8 @@ template <typename A> inline bool HasCUDADeviceAttrs(const A &expr) {
 // device attribute.
 template <typename A, typename B>
 inline bool IsCUDADataTransfer(const A &lhs, const B &rhs) {
-  int lhsNbManagedSymbols = {GetNbOfCUDAManagedOrUnifiedSymbols(lhs)};
-  int rhsNbManagedSymbols = {GetNbOfCUDAManagedOrUnifiedSymbols(rhs)};
+  int lhsNbManagedSymbols{GetNbOfCUDAManagedOrUnifiedSymbols(lhs)};
+  int rhsNbManagedSymbols{GetNbOfCUDAManagedOrUnifiedSymbols(rhs)};
   int rhsNbSymbols{GetNbOfCUDADeviceSymbols(rhs)};
 
   // Special cases performed on the host:

diff  --git a/flang/include/flang/Parser/parse-tree.h b/flang/include/flang/Parser/parse-tree.h
index 6ac195f9357a5..8712f861b82d8 100644
--- a/flang/include/flang/Parser/parse-tree.h
+++ b/flang/include/flang/Parser/parse-tree.h
@@ -1481,8 +1481,11 @@ WRAPPER_CLASS(ContiguousStmt, std::list<ObjectName>);
 // R846 int-constant-subobject -> constant-subobject
 using ConstantSubobject = Constant<common::Indirection<Designator>>;
 
-// Represents an analyzed expression
+// Represent an analyzed expression
 using TypedExpr = common::ForwardOwningPointer<evaluate::GenericExprWrapper>;
+using TypedCall = common::ForwardOwningPointer<evaluate::ProcedureRef>;
+using TypedAssignment =
+    common::ForwardOwningPointer<evaluate::GenericAssignmentWrapper>;
 
 // R845 data-stmt-constant ->
 //        scalar-constant | scalar-constant-subobject |
@@ -2025,8 +2028,6 @@ struct DeallocateStmt {
 // R1032 assignment-stmt -> variable = expr
 struct AssignmentStmt {
   TUPLE_CLASS_BOILERPLATE(AssignmentStmt);
-  using TypedAssignment =
-      common::ForwardOwningPointer<evaluate::GenericAssignmentWrapper>;
   mutable TypedAssignment typedAssignment;
   std::tuple<Variable, Expr> t;
 };
@@ -2053,7 +2054,7 @@ struct PointerAssignmentStmt {
     std::variant<std::list<BoundsRemapping>, std::list<BoundsSpec>> u;
   };
   TUPLE_CLASS_BOILERPLATE(PointerAssignmentStmt);
-  mutable AssignmentStmt::TypedAssignment typedAssignment;
+  mutable TypedAssignment typedAssignment;
   std::tuple<DataRef, Bounds, Expr> t;
 };
 
@@ -3298,8 +3299,7 @@ struct CallStmt {
   Call call;
   std::optional<Chevrons> chevrons;
   CharBlock source;
-  mutable common::ForwardOwningPointer<evaluate::ProcedureRef>
-      typedCall; // filled by semantics
+  mutable TypedCall typedCall; // filled by semantics
 };
 
 // R1529 function-subprogram ->
@@ -5350,7 +5350,7 @@ struct OpenMPAtomicConstruct : public OmpBlockConstruct {
 
     struct Op {
       int what;
-      AssignmentStmt::TypedAssignment assign;
+      TypedAssignment assign;
     };
     TypedExpr atom, cond;
     Op op0, op1;

diff  --git a/flang/include/flang/Parser/tools.h b/flang/include/flang/Parser/tools.h
index d105f03dd31d3..5e705a8b47276 100644
--- a/flang/include/flang/Parser/tools.h
+++ b/flang/include/flang/Parser/tools.h
@@ -136,11 +136,20 @@ template <typename A>
 struct HasSource<A, decltype(static_cast<void>(A::source), 0)>
     : std::true_type {};
 
-// Detects parse tree nodes with "typedExpr" members.
+// Detects parse tree nodes with "typedExpr", "typedCall", &c. members.
 template <typename A, typename = int> struct HasTypedExpr : std::false_type {};
 template <typename A>
 struct HasTypedExpr<A, decltype(static_cast<void>(A::typedExpr), 0)>
     : std::true_type {};
+template <typename A, typename = int> struct HasTypedCall : std::false_type {};
+template <typename A>
+struct HasTypedCall<A, decltype(static_cast<void>(A::typedCall), 0)>
+    : std::true_type {};
+template <typename A, typename = int>
+struct HasTypedAssignment : std::false_type {};
+template <typename A>
+struct HasTypedAssignment<A, decltype(static_cast<void>(A::typedAssignment), 0)>
+    : std::true_type {};
 
 // GetSource()
 

diff  --git a/flang/include/flang/Semantics/expression.h b/flang/include/flang/Semantics/expression.h
index 639ef99d2d936..e32158f3daa32 100644
--- a/flang/include/flang/Semantics/expression.h
+++ b/flang/include/flang/Semantics/expression.h
@@ -464,6 +464,13 @@ 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 &);
+
 // Semantic analysis of all expressions in a parse tree, which becomes
 // decorated with typed representations for top-level expressions.
 class ExprChecker {
@@ -475,11 +482,11 @@ class ExprChecker {
   bool Walk(const parser::Program &);
 
   bool Pre(const parser::Expr &x) {
-    exprAnalyzer_.Analyze(x);
+    AnalyzeAndNoteUses(x);
     return false;
   }
   bool Pre(const parser::Variable &x) {
-    exprAnalyzer_.Analyze(x);
+    AnalyzeAndNoteUses(x);
     return false;
   }
   bool Pre(const parser::Selector &x) {
@@ -491,11 +498,11 @@ class ExprChecker {
     return false;
   }
   bool Pre(const parser::AllocateObject &x) {
-    exprAnalyzer_.Analyze(x);
+    AnalyzeAndNoteUses(x);
     return false;
   }
   bool Pre(const parser::PointerObject &x) {
-    exprAnalyzer_.Analyze(x);
+    AnalyzeAndNoteUses(x);
     return false;
   }
   bool Pre(const parser::DataStmtObject &);
@@ -503,15 +510,15 @@ class ExprChecker {
   bool Pre(const parser::DataImpliedDo &);
 
   bool Pre(const parser::CallStmt &x) {
-    exprAnalyzer_.Analyze(x);
+    AnalyzeAndNoteUses(x);
     return false;
   }
   bool Pre(const parser::AssignmentStmt &x) {
-    exprAnalyzer_.Analyze(x);
+    AnalyzeAndNoteUses(x);
     return false;
   }
   bool Pre(const parser::PointerAssignmentStmt &x) {
-    exprAnalyzer_.Analyze(x);
+    AnalyzeAndNoteUses(x);
     return false;
   }
 
@@ -552,27 +559,37 @@ class ExprChecker {
   }
 
   template <typename A> bool Pre(const parser::Scalar<A> &x) {
-    exprAnalyzer_.Analyze(x);
+    AnalyzeAndNoteUses(x);
     return false;
   }
   template <typename A> bool Pre(const parser::Constant<A> &x) {
-    exprAnalyzer_.Analyze(x);
+    AnalyzeAndNoteUses(x);
     return false;
   }
   template <typename A> bool Pre(const parser::Integer<A> &x) {
-    exprAnalyzer_.Analyze(x);
+    AnalyzeAndNoteUses(x);
     return false;
   }
   template <typename A> bool Pre(const parser::Logical<A> &x) {
-    exprAnalyzer_.Analyze(x);
+    AnalyzeAndNoteUses(x);
     return false;
   }
   template <typename A> bool Pre(const parser::DefaultChar<A> &x) {
-    exprAnalyzer_.Analyze(x);
+    AnalyzeAndNoteUses(x);
     return false;
   }
 
 private:
+  template <typename A> void AnalyzeAndNoteUses(const A &x) {
+    exprAnalyzer_.Analyze(x);
+    if constexpr (parser::HasTypedExpr<A>::value) {
+      NoteUsedSymbols(context_, x.typedExpr);
+    } else if constexpr (parser::HasTypedCall<A>::value) {
+      NoteUsedSymbols(context_, x.typedCall);
+    } else if constexpr (parser::HasTypedAssignment<A>::value) {
+      NoteUsedSymbols(context_, x.typedAssignment);
+    }
+  }
   bool InWhereBody() const { return whereDepth_ > 0; }
 
   SemanticsContext &context_;

diff  --git a/flang/include/flang/Semantics/semantics.h b/flang/include/flang/Semantics/semantics.h
index c03d0a02d7faf..88bc23380b22d 100644
--- a/flang/include/flang/Semantics/semantics.h
+++ b/flang/include/flang/Semantics/semantics.h
@@ -331,6 +331,8 @@ class SemanticsContext {
 
   void NoteDefinedSymbol(const Symbol &);
   bool IsSymbolDefined(const Symbol &) const;
+  void NoteUsedSymbol(const Symbol &);
+  bool IsSymbolUsed(const Symbol &) const;
 
   void DumpSymbols(llvm::raw_ostream &);
 
@@ -390,6 +392,7 @@ class SemanticsContext {
   ModuleDependences moduleDependences_;
   std::map<const Symbol *, SourceName> moduleFileOutputRenamings_;
   UnorderedSymbolSet isDefined_;
+  UnorderedSymbolSet isUsed_;
   std::list<ProgramTree> programTrees_;
 };
 

diff  --git a/flang/include/flang/Support/Fortran-features.h b/flang/include/flang/Support/Fortran-features.h
index ef5c1a84ba3d7..e81695c749784 100644
--- a/flang/include/flang/Support/Fortran-features.h
+++ b/flang/include/flang/Support/Fortran-features.h
@@ -79,7 +79,8 @@ ENUM_CLASS(UsageWarning, Portability, PointerToUndefinable,
     CompatibleDeclarationsFromDistinctModules, ConstantIsContiguous,
     NullActualForDefaultIntentAllocatable, UseAssociationIntoSameNameSubprogram,
     HostAssociatedIntentOutInSpecExpr, NonVolatilePointerToVolatile,
-    RealConstantWidening, VolatileOrAsynchronousTemporary)
+    RealConstantWidening, VolatileOrAsynchronousTemporary, UnusedVariable,
+    UsedUndefinedVariable)
 
 using LanguageFeatures = EnumSet<LanguageFeature, LanguageFeature_enumSize>;
 using UsageWarnings = EnumSet<UsageWarning, UsageWarning_enumSize>;

diff  --git a/flang/lib/Evaluate/tools.cpp b/flang/lib/Evaluate/tools.cpp
index a0035ae330e35..37f718c98ebb5 100644
--- a/flang/lib/Evaluate/tools.cpp
+++ b/flang/lib/Evaluate/tools.cpp
@@ -1094,6 +1094,9 @@ template semantics::UnorderedSymbolSet CollectSymbols(
     const Expr<SomeInteger> &);
 template semantics::UnorderedSymbolSet CollectSymbols(
     const Expr<SubscriptInteger> &);
+template semantics::UnorderedSymbolSet CollectSymbols(
+    const ProcedureDesignator &);
+template semantics::UnorderedSymbolSet CollectSymbols(const Assignment &);
 
 struct CollectCudaSymbolsHelper : public SetTraverse<CollectCudaSymbolsHelper,
                                       semantics::UnorderedSymbolSet> {

diff  --git a/flang/lib/Lower/OpenMP/Atomic.cpp b/flang/lib/Lower/OpenMP/Atomic.cpp
index 3ab8a5891e8a6..f31de82fc2a5f 100644
--- a/flang/lib/Lower/OpenMP/Atomic.cpp
+++ b/flang/lib/Lower/OpenMP/Atomic.cpp
@@ -80,7 +80,7 @@ dumpAtomicAnalysis(const parser::OpenMPAtomicConstruct::Analysis &analysis) {
     }
     return "<null>"s;
   };
-  auto assignStr = [&](const parser::AssignmentStmt::TypedAssignment &assign) {
+  auto assignStr = [&](const parser::TypedAssignment &assign) {
     if (auto *maybe = assign.get(); maybe && maybe->v) {
       std::string str;
       llvm::raw_string_ostream os(str);

diff  --git a/flang/lib/Semantics/check-allocate.cpp b/flang/lib/Semantics/check-allocate.cpp
index a411e20557456..7f099d51221c0 100644
--- a/flang/lib/Semantics/check-allocate.cpp
+++ b/flang/lib/Semantics/check-allocate.cpp
@@ -566,7 +566,6 @@ bool AllocationCheckerHelper::RunChecks(SemanticsContext &context) {
             *type_, allocateInfo_.sourceExprType.value())) { // F'2023 C950
       context.Warn(common::LanguageFeature::AllocateToOtherLength, name_.source,
           "Character length of allocatable object in ALLOCATE should be the same as the SOURCE or MOLD"_port_en_US);
-      return false;
     }
   }
   // Shape related checks

diff  --git a/flang/lib/Semantics/check-omp-atomic.cpp b/flang/lib/Semantics/check-omp-atomic.cpp
index 9a4cb84ad3c2c..2218acce95372 100644
--- a/flang/lib/Semantics/check-omp-atomic.cpp
+++ b/flang/lib/Semantics/check-omp-atomic.cpp
@@ -260,7 +260,7 @@ static std::optional<evaluate::Assignment> GetEvaluateAssignment(
   using AssignmentStmt = common::Indirection<parser::AssignmentStmt>;
   using PointerAssignmentStmt =
       common::Indirection<parser::PointerAssignmentStmt>;
-  using TypedAssignment = parser::AssignmentStmt::TypedAssignment;
+  using TypedAssignment = parser::TypedAssignment;
 
   return common::visit(
       [](auto &&s) -> std::optional<evaluate::Assignment> {
@@ -426,7 +426,7 @@ static void SetExpr(parser::TypedExpr &expr, MaybeExpr value) {
   }
 }
 
-static void SetAssignment(parser::AssignmentStmt::TypedAssignment &assign,
+static void SetAssignment(parser::TypedAssignment &assign,
     std::optional<evaluate::Assignment> value) {
   if (value) {
     assign.Reset(new evaluate::GenericAssignmentWrapper(std::move(value)),

diff  --git a/flang/lib/Semantics/definable.cpp b/flang/lib/Semantics/definable.cpp
index 08cb268b318ae..de16422b89abd 100644
--- a/flang/lib/Semantics/definable.cpp
+++ b/flang/lib/Semantics/definable.cpp
@@ -129,8 +129,7 @@ static std::optional<parser::Message> WhyNotDefinableBase(parser::CharBlock at,
         at, "'%s' is an INTENT(IN) dummy argument"_en_US, original);
   } else if (acceptAllocatable && IsAllocatable(ultimate) &&
       !flags.test(DefinabilityFlag::SourcedAllocation)) {
-    // allocating a function result doesn't count as a def'n
-    // unless there's SOURCE=
+    // allocating an allocatable doesn't count as a def'n unless there's SOURCE=
   } else if (!flags.test(DefinabilityFlag::DoNotNoteDefinition)) {
     scope.context().NoteDefinedSymbol(ultimate);
   }

diff  --git a/flang/lib/Semantics/expression.cpp b/flang/lib/Semantics/expression.cpp
index 6f5d0bf9eb242..e301190629234 100644
--- a/flang/lib/Semantics/expression.cpp
+++ b/flang/lib/Semantics/expression.cpp
@@ -5345,6 +5345,96 @@ 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);
+  }
+}
+
 ExprChecker::ExprChecker(SemanticsContext &context) : context_{context} {}
 
 bool ExprChecker::Pre(const parser::DataStmtObject &obj) {

diff  --git a/flang/lib/Semantics/resolve-names.cpp b/flang/lib/Semantics/resolve-names.cpp
index 5cc887791048d..b341dc08eecbf 100644
--- a/flang/lib/Semantics/resolve-names.cpp
+++ b/flang/lib/Semantics/resolve-names.cpp
@@ -7501,7 +7501,12 @@ Symbol *DeclarationVisitor::DeclareStatementEntity(
       SayAlreadyDeclared(name, *prev);
       return nullptr;
     }
-    name.symbol = nullptr;
+    // Inhibit diagnostics about an unused local symbol here, since
+    // this one may well have been declared solely to determine the
+    // type of an implied DO index.  Some compilers don't yet support
+    // an explicit "integer(k)::" in an implied DO.
+    context().NoteDefinedSymbol(*prev);
+    name.symbol = nullptr; // undo the "FindSymbol()" above
     // F'2023 19.4 p5 ambiguous rule about outer declarations
     declTypeSpec = prev->GetType();
   }

diff  --git a/flang/lib/Semantics/semantics.cpp b/flang/lib/Semantics/semantics.cpp
index 2606d997b1cd7..d9b14f7d17f0c 100644
--- a/flang/lib/Semantics/semantics.cpp
+++ b/flang/lib/Semantics/semantics.cpp
@@ -160,22 +160,23 @@ class MiscChecker : public virtual BaseChecker {
   SemanticsContext &context_;
 };
 
+static bool WasDefined(const SemanticsContext &context, const Symbol &symbol) {
+  return context.IsSymbolDefined(symbol) ||
+      IsInitialized(symbol, /*ignoreDataStatements=*/true,
+          /*ignoreAllocatable=*/true, /*ignorePointer=*/true);
+}
+
 static void WarnUndefinedFunctionResult(
     SemanticsContext &context, const Scope &scope) {
-  auto WasDefined{[&context](const Symbol &symbol) {
-    return context.IsSymbolDefined(symbol) ||
-        IsInitialized(symbol, /*ignoreDataStatements=*/true,
-            /*ignoreAllocatable=*/true, /*ignorePointer=*/true);
-  }};
   if (const Symbol * symbol{scope.symbol()}) {
     if (const auto *subp{symbol->detailsIf<SubprogramDetails>()}) {
       if (subp->isFunction() && !subp->isInterface() && !subp->stmtFunction()) {
-        bool wasDefined{WasDefined(subp->result())};
+        bool wasDefined{WasDefined(context, subp->result())};
         if (!wasDefined) {
           // Definitions of ENTRY result variables also count.
           for (const auto &pair : scope) {
             const Symbol &local{*pair.second};
-            if (IsFunctionResult(local) && WasDefined(local)) {
+            if (IsFunctionResult(local) && WasDefined(context, local)) {
               wasDefined = true;
               break;
             }
@@ -195,6 +196,43 @@ static void WarnUndefinedFunctionResult(
   }
 }
 
+static void WarnUnusedOrUndefinedLocal(
+    SemanticsContext &context, const Scope &scope) {
+  if (scope.kind() == Scope::Kind::Subprogram ||
+      scope.kind() == Scope::Kind::MainProgram ||
+      scope.kind() == Scope::Kind::BlockConstruct) {
+    for (const auto &[_, symbolRef] : scope) {
+      const Symbol &symbol{*symbolRef};
+      if ((symbol.has<semantics::ObjectEntityDetails>() ||
+              (symbol.has<semantics::ProcEntityDetails>() &&
+                  IsProcedurePointer(symbol))) &&
+          !IsFunctionResult(symbol) && !IsNamedConstant(symbol) &&
+          !IsDummy(symbol) && !FindEquivalenceSet(symbol) &&
+          !FindCommonBlockContaining(symbol)) {
+        if (context.IsSymbolUsed(symbol)) {
+          if (!WasDefined(context, symbol)) {
+            context.Warn(common::UsageWarning::UsedUndefinedVariable,
+                symbol.name(),
+                "Value of uninitialized local variable '%s' is used but never defined"_warn_en_US,
+                symbol.name());
+          }
+        } else {
+          if (!context.IsSymbolDefined(symbol)) { // ignore initialization
+            context.Warn(common::UsageWarning::UnusedVariable, symbol.name(),
+                "Value of local variable '%s' is never used"_warn_en_US,
+                symbol.name());
+          }
+        }
+      }
+    }
+  }
+  if (!scope.IsModuleFile()) {
+    for (const Scope &child : scope.children()) {
+      WarnUnusedOrUndefinedLocal(context, child);
+    }
+  }
+}
+
 using StatementSemanticsPass1 = ExprChecker;
 using StatementSemanticsPass2 = SemanticsVisitor<AllocateChecker,
     ArithmeticIfStmtChecker, AssignmentChecker, CaseChecker, CoarrayChecker,
@@ -224,6 +262,9 @@ static bool PerformStatementSemantics(
   if (!context.messages().AnyFatalError()) {
     WarnUndefinedFunctionResult(context, context.globalScope());
   }
+  if (!context.messages().AnyFatalError()) {
+    WarnUnusedOrUndefinedLocal(context, context.globalScope());
+  }
   if (!context.AnyFatalError()) {
     pass2.CompileDataInitializationsIntoInitializers();
   }
@@ -779,4 +820,12 @@ bool SemanticsContext::IsSymbolDefined(const Symbol &symbol) const {
   return isDefined_.find(symbol) != isDefined_.end();
 }
 
+void SemanticsContext::NoteUsedSymbol(const Symbol &symbol) {
+  isUsed_.insert(symbol);
+}
+
+bool SemanticsContext::IsSymbolUsed(const Symbol &symbol) const {
+  return isUsed_.find(symbol) != isUsed_.end();
+}
+
 } // namespace Fortran::semantics

diff  --git a/flang/lib/Support/Fortran-features.cpp b/flang/lib/Support/Fortran-features.cpp
index 4a6fb8d75a135..e01ad1396c381 100644
--- a/flang/lib/Support/Fortran-features.cpp
+++ b/flang/lib/Support/Fortran-features.cpp
@@ -152,6 +152,7 @@ LanguageFeatureControl::LanguageFeatureControl() {
   // New warnings, on by default
   warnLanguage_.set(LanguageFeature::SavedLocalInSpecExpr);
   warnLanguage_.set(LanguageFeature::NullActualForAllocatable);
+  warnUsage_.set(UsageWarning::UsedUndefinedVariable);
 }
 
 std::optional<LanguageControlFlag> LanguageFeatureControl::FindWarning(

diff  --git a/flang/test/Driver/disable-diagnostic.f90 b/flang/test/Driver/disable-diagnostic.f90
index 849489377da12..f743b1ac13c73 100644
--- a/flang/test/Driver/disable-diagnostic.f90
+++ b/flang/test/Driver/disable-diagnostic.f90
@@ -7,13 +7,11 @@
 ! ERROR2: error: Unknown diagnostic option: -WKnown-Bad-Implicit-Interface
 
 program disable_diagnostic
-  REAL :: x
-  INTEGER :: y
-   ! CHECK-NOT: warning
+  ! CHECK-NOT: warning
   ! WARN: warning: If the procedure's interface were explicit, this reference would be in error
-  call sub(x)
+  call sub(1.)
   ! WARN: warning: If the procedure's interface were explicit, this reference would be in error
-  call sub(y)
+  call sub(1)
 end program disable_diagnostic
 
 subroutine sub()

diff  --git a/flang/test/Semantics/OpenMP/copying.f90 b/flang/test/Semantics/OpenMP/copying.f90
index d79027361f2ab..a00de97847817 100644
--- a/flang/test/Semantics/OpenMP/copying.f90
+++ b/flang/test/Semantics/OpenMP/copying.f90
@@ -1,4 +1,4 @@
-! RUN: %python %S/../test_errors.py %s %flang -fopenmp -Werror -pedantic
+! RUN: %python %S/../test_errors.py %s %flang -fopenmp -Werror -pedantic -Wno-unused-variable
 ! OpenMP Version 5.0
 ! 2.19.4.4 firstprivate Clause
 ! 2.19.4.5 lastprivate Clause

diff  --git a/flang/test/Semantics/bindings03.f90 b/flang/test/Semantics/bindings03.f90
index b03caf0ac452f..1aa657cd667ff 100644
--- a/flang/test/Semantics/bindings03.f90
+++ b/flang/test/Semantics/bindings03.f90
@@ -14,6 +14,7 @@ subroutine sub(x)
 program test
   use m
   procedure(sub), pointer :: p
+  !WARNING: Value of local variable 'x' is never used [-Wunused-variable]
   type(t) x
   !PORTABILITY: Procedure binding 'sub' used as target of a pointer assignment [-Wbinding-as-procedure]
   p => x%sub

diff  --git a/flang/test/Semantics/kinds05b.f90 b/flang/test/Semantics/kinds05b.f90
index 3f2d4605adbdf..725492ff3fa62 100644
--- a/flang/test/Semantics/kinds05b.f90
+++ b/flang/test/Semantics/kinds05b.f90
@@ -1,4 +1,4 @@
-! RUN: %python %S/test_errors.py %s %flang_fc1 -pedantic -Werror
+! RUN: %python %S/test_errors.py %s %flang_fc1 -pedantic -Werror -Wno-unused-variable
 ! Check that we get portability warning for the extension:
 !  - matching but non-'E' exponent letter together with kind-param
 

diff  --git a/flang/test/Semantics/long-name.f90 b/flang/test/Semantics/long-name.f90
index d5a795113e204..6d1f3ea73c506 100644
--- a/flang/test/Semantics/long-name.f90
+++ b/flang/test/Semantics/long-name.f90
@@ -1,4 +1,4 @@
-! RUN: %python %S/test_errors.py %s %flang_fc1 -Werror -pedantic
+! RUN: %python %S/test_errors.py %s %flang_fc1 -Werror -pedantic -Wno-unused-variable
 
 !PORTABILITY: AAAAAAAAAABBBBBBBBBBCCCCCCCCCCDDDDDDDDDDEEEEEEEEEEFFFFFFFFFFGGG1 has length 64, which is greater than the maximum name length 63 [-Wlong-names]
 program aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffffffffffggg1

diff  --git a/flang/test/Semantics/unused.f90 b/flang/test/Semantics/unused.f90
new file mode 100644
index 0000000000000..441d0054b8804
--- /dev/null
+++ b/flang/test/Semantics/unused.f90
@@ -0,0 +1,33 @@
+!RUN: %python %S/test_errors.py %s %flang_fc1 -Werror -Wunused-variable -Wused-undefined-variable
+subroutine testUnusedWarnings(dummyArgument)
+  common inCommon ! ok
+  !WARNING: Value of local variable 'uninitializedunused' is never used [-Wunused-variable]
+  real uninitializedUnused
+  !WARNING: Value of uninitialized local variable 'undefined' is used but never defined [-Wused-undefined-variable]
+  real undefined
+  integer :: initialized = 1 ! ok
+  !WARNING: Value of local variable 'unusedinitialized' is never used [-Wunused-variable]
+  integer :: unusedInitialized = 2
+  !WARNING: Value of local variable 'craypointer1' is never used [-Wunused-variable]
+  !WARNING: Value of local variable 'craypointee1' is never used [-Wunused-variable]
+  pointer (crayPointer1, crayPointee1)
+  !WARNING: Value of uninitialized local variable 'craypointer2' is used but never defined [-Wused-undefined-variable]
+  !WARNING: Value of uninitialized local variable 'craypointee2' is used but never defined [-Wused-undefined-variable]
+  pointer (crayPointer2, crayPointee2)
+  integer, parameter :: namedConstant = 123 ! ok
+  real, target :: target ! ok
+  real, pointer :: pointer ! ok
+  equivalence (eq1, eq2) ! ok
+  !WARNING: Value of uninitialized local variable 'a1' is used but never defined [-Wused-undefined-variable]
+  real, allocatable :: a1, a2, a3, a4, a5
+  !WARNING: Value of local variable 'b2' is never used [-Wunused-variable]
+  real b1, b2
+  allocate(a2, source=a1)
+  allocate(a3, source=a2)
+  do j = 1, 10 ! ok
+  end do
+  pointer => target
+  call move_alloc(a4, a5)
+  read(*,*) b1
+  print *, undefined, initialized, crayPointee2, a3, b1
+end


        


More information about the flang-commits mailing list