[clang-tools-extra] [clang-tidy] Add `modernize-use-std-interpolation` check (PR #228104)

Daniil Dudkin via cfe-commits cfe-commits at lists.llvm.org
Thu Oct 1 14:53:49 PDT 2026


https://github.com/unterumarmung updated https://github.com/llvm/llvm-project/pull/228104

>From 75d6cacfdeafbe0a8b102d842f6205382f3312ff Mon Sep 17 00:00:00 2001
From: Daniil Dudkin <unterumarmung at yandex.ru>
Date: Thu, 1 Oct 2026 18:09:51 +0300
Subject: [PATCH 1/3] [clang-tidy] Add modernize-use-std-interpolation check

Add a C++20 check that replaces manual midpoint and linear interpolation formulas with `std::midpoint` and `std::lerp`, inserting `<numeric>` and `<cmath>` as needed.

Fixes #189490

Assisted by ChatGPT
---
 .../clang-tidy/modernize/CMakeLists.txt       |   2 +
 .../modernize/ModernizeTidyModule.cpp         |   3 +
 .../modernize/UseStdInterpolationCheck.cpp    | 164 +++++
 .../modernize/UseStdInterpolationCheck.h      |  33 +
 clang-tools-extra/docs/ReleaseNotes.md        |   5 +
 .../docs/clang-tidy/checks/list.md            |   1 +
 .../checks/modernize/use-std-interpolation.md |  67 +++
 .../checkers/Inputs/Headers/std/cmath         |  10 +
 .../checkers/Inputs/Headers/std/numeric       |   6 +
 .../modernize/Inputs/use-std-interpolation.h  |  19 +
 .../use-std-interpolation-header.cpp          |   4 +
 .../modernize/use-std-interpolation.cpp       | 569 ++++++++++++++++++
 12 files changed, 883 insertions(+)
 create mode 100644 clang-tools-extra/clang-tidy/modernize/UseStdInterpolationCheck.cpp
 create mode 100644 clang-tools-extra/clang-tidy/modernize/UseStdInterpolationCheck.h
 create mode 100644 clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-interpolation.md
 create mode 100644 clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/std/cmath
 create mode 100644 clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/std/numeric
 create mode 100644 clang-tools-extra/test/clang-tidy/checkers/modernize/Inputs/use-std-interpolation.h
 create mode 100644 clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-interpolation-header.cpp
 create mode 100644 clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-interpolation.cpp

diff --git a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
index 23aeb393ad016..a7f6af3b3d151 100644
--- a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
@@ -49,6 +49,7 @@ add_clang_library(clangTidyModernizeModule STATIC
   UseStartsEndsWithCheck.cpp
   UseStdBitCheck.cpp
   UseStdFormatCheck.cpp
+  UseStdInterpolationCheck.cpp
   UseStdNumbersCheck.cpp
   UseStdPrintCheck.cpp
   UseStringViewCheck.cpp
@@ -76,4 +77,5 @@ clang_target_link_libraries(clangTidyModernizeModule
   clangBasic
   clangLex
   clangTooling
+  clangTransformer
   )
diff --git a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
index 62676469aefe7..56f916ffc0544 100644
--- a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
@@ -49,6 +49,7 @@
 #include "UseStartsEndsWithCheck.h"
 #include "UseStdBitCheck.h"
 #include "UseStdFormatCheck.h"
+#include "UseStdInterpolationCheck.h"
 #include "UseStdNumbersCheck.h"
 #include "UseStdPrintCheck.h"
 #include "UseStringViewCheck.h"
@@ -100,6 +101,8 @@ class ModernizeModule : public ClangTidyModule {
         "modernize-use-starts-ends-with");
     CheckFactories.registerCheck<UseStdBitCheck>("modernize-use-std-bit");
     CheckFactories.registerCheck<UseStdFormatCheck>("modernize-use-std-format");
+    CheckFactories.registerCheck<UseStdInterpolationCheck>(
+        "modernize-use-std-interpolation");
     CheckFactories.registerCheck<UseStdNumbersCheck>(
         "modernize-use-std-numbers");
     CheckFactories.registerCheck<UseStdPrintCheck>("modernize-use-std-print");
diff --git a/clang-tools-extra/clang-tidy/modernize/UseStdInterpolationCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStdInterpolationCheck.cpp
new file mode 100644
index 0000000000000..5febc5835b8ff
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/modernize/UseStdInterpolationCheck.cpp
@@ -0,0 +1,164 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "UseStdInterpolationCheck.h"
+#include "../utils/Matchers.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Tooling/Transformer/RewriteRule.h"
+#include "clang/Tooling/Transformer/Stencil.h"
+
+using namespace clang::ast_matchers;
+using namespace clang::transformer;
+
+namespace clang::tidy::modernize {
+namespace {
+
+AST_MATCHER(Expr, hasSideEffects) {
+  return Node.HasSideEffects(Finder->getASTContext());
+}
+
+AST_MATCHER(Expr, isMacroExpanded) {
+  if (Node.getBeginLoc().isMacroID() || Node.getEndLoc().isMacroID())
+    return true;
+  if (const auto *Operator = dyn_cast<BinaryOperator>(&Node))
+    return Operator->getOperatorLoc().isMacroID();
+  return false;
+}
+
+AST_MATCHER_P(QualType, hasUnqualifiedCanonicalType,
+              ast_matchers::internal::Matcher<QualType>, InnerMatcher) {
+  return InnerMatcher.matches(Node.getCanonicalType().getUnqualifiedType(),
+                              Finder, Builder);
+}
+
+AST_MATCHER(QualType, isInterpolationFloatingType) {
+  return Node->isSpecificBuiltinType(BuiltinType::Float) ||
+         Node->isSpecificBuiltinType(BuiltinType::Double) ||
+         Node->isSpecificBuiltinType(BuiltinType::LongDouble);
+}
+
+AST_MATCHER(QualType, isInterpolationIntegerType) {
+  return Node->isSpecificBuiltinType(BuiltinType::Int) ||
+         Node->isSpecificBuiltinType(BuiltinType::UInt) ||
+         Node->isSpecificBuiltinType(BuiltinType::Long) ||
+         Node->isSpecificBuiltinType(BuiltinType::ULong) ||
+         Node->isSpecificBuiltinType(BuiltinType::LongLong) ||
+         Node->isSpecificBuiltinType(BuiltinType::ULongLong);
+}
+
+using BinaryOperatorMatcher = ast_matchers::internal::Matcher<BinaryOperator>;
+
+struct InterpolationMatchers {
+  BinaryOperatorMatcher Midpoint;
+  BinaryOperatorMatcher Lerp;
+};
+
+} // namespace
+
+static InterpolationMatchers makeInterpolationMatchers() {
+  const auto SameType = hasType(qualType(hasUnqualifiedCanonicalType(
+      qualType(equalsBoundNode("calculationType")))));
+  const auto Start = ignoringParenImpCasts(expr(SameType).bind("start"));
+  const auto End = ignoringParenImpCasts(expr(SameType).bind("end"));
+  const auto Factor = ignoringParenImpCasts(expr(SameType).bind("factor"));
+  const auto RepeatedStart = ignoringParenImpCasts(
+      expr(matchers::isStatementIdenticalToBoundNode("start")));
+  const auto RepeatedFactor = ignoringParenImpCasts(
+      expr(matchers::isStatementIdenticalToBoundNode("factor")));
+  const auto Two = ignoringParenImpCasts(
+      expr(anyOf(integerLiteral(equals(2)), floatLiteral(equals(2.0)))));
+  const auto Half = ignoringParenImpCasts(floatLiteral(equals(0.5)));
+  const auto One = ignoringParenImpCasts(
+      expr(anyOf(integerLiteral(equals(1)), floatLiteral(equals(1.0)))));
+
+  // Midpoints: (a + b) / 2 and a + (b - a) / 2, also using * 0.5.
+  const auto Sum = ignoringParenImpCasts(binaryOperator(
+      hasOperatorName("+"), SameType, hasLHS(Start), hasRHS(End)));
+  const auto Difference = ignoringParenImpCasts(binaryOperator(
+      hasOperatorName("-"), SameType, hasLHS(End), hasRHS(RepeatedStart)));
+  const auto HalfSum = binaryOperator(
+      anyOf(allOf(hasOperatorName("/"), hasLHS(Sum), hasRHS(Two)),
+            allOf(hasOperatorName("*"), hasOperands(Sum, Half))));
+  const auto HalfDifference = ignoringParenImpCasts(binaryOperator(
+      anyOf(allOf(hasOperatorName("/"), hasLHS(Difference), hasRHS(Two)),
+            allOf(hasOperatorName("*"), hasOperands(Difference, Half)))));
+  const auto DifferenceMidpoint =
+      binaryOperator(hasOperatorName("+"), hasOperands(Start, HalfDifference));
+
+  // Difference-form interpolation: a + (b - a) * t.
+  const auto ScaledDifference = ignoringParenImpCasts(binaryOperator(
+      hasOperatorName("*"), SameType, hasOperands(Difference, Factor)));
+  const auto DifferenceLerp = binaryOperator(
+      hasOperatorName("+"), hasOperands(Start, ScaledDifference));
+
+  // Weighted interpolation: (1 - t) * a + t * b.
+  const auto Complement = ignoringParenImpCasts(binaryOperator(
+      hasOperatorName("-"), SameType, hasLHS(One), hasRHS(Factor)));
+  const auto WeightedStart = ignoringParenImpCasts(binaryOperator(
+      hasOperatorName("*"), SameType, hasOperands(Start, Complement)));
+  const auto WeightedEnd = ignoringParenImpCasts(binaryOperator(
+      hasOperatorName("*"), SameType, hasOperands(End, RepeatedFactor)));
+  const auto WeightedLerp = binaryOperator(
+      hasOperatorName("+"), hasOperands(WeightedStart, WeightedEnd));
+
+  const auto NumericType = hasType(qualType(
+      hasUnqualifiedCanonicalType(qualType(anyOf(isInterpolationIntegerType(),
+                                                 isInterpolationFloatingType()))
+                                      .bind("calculationType"))));
+  const auto FloatingType = hasType(qualType(hasUnqualifiedCanonicalType(
+      qualType(isInterpolationFloatingType()).bind("calculationType"))));
+  const auto Midpoint =
+      binaryOperator(NumericType, anyOf(HalfSum, DifferenceMidpoint));
+  const auto Lerp =
+      binaryOperator(FloatingType, anyOf(DifferenceLerp, WeightedLerp));
+  return {Midpoint, Lerp};
+}
+
+static BinaryOperatorMatcher eligibleCalculation() {
+  return binaryOperator(
+      unless(isExpansionInSystemHeader()), unless(isInTemplateInstantiation()),
+      unless(isTypeDependent()), unless(isValueDependent()),
+      unless(isMacroExpanded()), unless(hasSideEffects()),
+      unless(hasDescendant(expr(isMacroExpanded()))),
+      unless(hasDescendant(cxxOperatorCallExpr())),
+      unless(hasDescendant(cxxMemberCallExpr(callee(cxxConversionDecl())))),
+      unless(hasAncestor(expr(matchers::hasUnevaluatedContext()))),
+      unless(hasAncestor(typeLoc())));
+}
+
+static RewriteRuleWith<std::string> makeInterpolationRule() {
+  const auto Patterns = makeInterpolationMatchers();
+  const auto Eligible = eligibleCalculation();
+  const auto Pattern = binaryOperator(anyOf(Patterns.Midpoint, Patterns.Lerp));
+  // Only replace innermost calculations to avoid overlapping edits. The
+  // bindings used to test descendants must not escape into the replacement.
+  const auto Innermost =
+      unless(hasDescendant(binaryOperator(Pattern, Eligible)));
+
+  return applyFirst(
+      {makeRule(
+           binaryOperator(Patterns.Midpoint, Eligible, Innermost),
+           {changeTo(cat("std::midpoint(", expression("start"), ", ",
+                         expression("end"), ")")),
+            addInclude("numeric", IncludeFormat::Angled)},
+           cat("use 'std::midpoint' instead of manual midpoint calculation")),
+       makeRule(
+           binaryOperator(Patterns.Lerp, Eligible, Innermost),
+           {changeTo(cat("std::lerp(", expression("start"), ", ",
+                         expression("end"), ", ", expression("factor"), ")")),
+            addInclude("cmath", IncludeFormat::Angled)},
+           cat("use 'std::lerp' instead of manual linear interpolation"))});
+}
+
+UseStdInterpolationCheck::UseStdInterpolationCheck(StringRef Name,
+                                                   ClangTidyContext *Context)
+    : utils::TransformerClangTidyCheck(makeInterpolationRule(), Name, Context) {
+}
+
+} // namespace clang::tidy::modernize
diff --git a/clang-tools-extra/clang-tidy/modernize/UseStdInterpolationCheck.h b/clang-tools-extra/clang-tidy/modernize/UseStdInterpolationCheck.h
new file mode 100644
index 0000000000000..0177ac9f11872
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/modernize/UseStdInterpolationCheck.h
@@ -0,0 +1,33 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USESTDINTERPOLATIONCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USESTDINTERPOLATIONCHECK_H
+
+#include "../utils/TransformerClangTidyCheck.h"
+
+namespace clang::tidy::modernize {
+
+/// Replaces manual midpoint and linear interpolation calculations.
+///
+/// For the user-facing documentation see:
+/// https://clang.llvm.org/extra/clang-tidy/checks/modernize/use-std-interpolation.html
+class UseStdInterpolationCheck : public utils::TransformerClangTidyCheck {
+public:
+  UseStdInterpolationCheck(StringRef Name, ClangTidyContext *Context);
+  bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
+    return LangOpts.CPlusPlus20;
+  }
+  std::optional<TraversalKind> getCheckTraversalKind() const override {
+    return TK_IgnoreUnlessSpelledInSource;
+  }
+};
+
+} // namespace clang::tidy::modernize
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USESTDINTERPOLATIONCHECK_H
diff --git a/clang-tools-extra/docs/ReleaseNotes.md b/clang-tools-extra/docs/ReleaseNotes.md
index c2ee1b06c43e0..0bb4ab5ed1f59 100644
--- a/clang-tools-extra/docs/ReleaseNotes.md
+++ b/clang-tools-extra/docs/ReleaseNotes.md
@@ -134,6 +134,11 @@ infrastructure are described first, followed by tool-specific sections.
 
   Detects malformed regex patterns defined in a single string literal.
 
+- New {doc}`modernize-use-std-interpolation
+  <clang-tidy/checks/modernize/use-std-interpolation>` check.
+
+  Replaces manual midpoint and linear interpolation calculations.
+
 - New {doc}`modernize-use-to-underlying
   <clang-tidy/checks/modernize/use-to-underlying>` check.
 
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.md b/clang-tools-extra/docs/clang-tidy/checks/list.md
index a74a26e691053..128db420d0c19 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.md
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.md
@@ -334,6 +334,7 @@ readability/*
 | {doc}`modernize-use-starts-ends-with <modernize/use-starts-ends-with>` | Yes |
 | {doc}`modernize-use-std-bit <modernize/use-std-bit>` | Yes |
 | {doc}`modernize-use-std-format <modernize/use-std-format>` | Yes |
+| {doc}`modernize-use-std-interpolation <modernize/use-std-interpolation>` | Yes |
 | {doc}`modernize-use-std-numbers <modernize/use-std-numbers>` | Yes |
 | {doc}`modernize-use-std-print <modernize/use-std-print>` | Yes |
 | {doc}`modernize-use-string-view <modernize/use-string-view>` | Yes |
diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-interpolation.md b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-interpolation.md
new file mode 100644
index 0000000000000..f639e64dbaae7
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-interpolation.md
@@ -0,0 +1,67 @@
+```{title} clang-tidy - modernize-use-std-interpolation
+```
+
+# modernize-use-std-interpolation
+
+Replaces manual midpoint and linear interpolation calculations.
+
+The check suggests `std::midpoint` from `<numeric>` and `std::lerp` from `<cmath>`
+to express the calculation's intent and avoid intermediate overflow. It requires
+C++20 or later.
+
+For example:
+
+```c++
+int midpoint(int a, int b) {
+  return (a + b) / 2;
+}
+
+double interpolate(double a, double b, double t) {
+  return a + (b - a) * t;
+}
+```
+
+becomes:
+
+```c++
+#include <cmath>
+#include <numeric>
+
+int midpoint(int a, int b) {
+  return std::midpoint(a, b);
+}
+
+double interpolate(double a, double b, double t) {
+  return std::lerp(a, b, t);
+}
+```
+
+The following expressions are recognized:
+
+| Expression | Replacement |
+| --- | --- |
+| `(a + b) / 2` | `std::midpoint(a, b)` |
+| `a + (b - a) / 2` | `std::midpoint(a, b)` |
+| `(a + b) * 0.5` | `std::midpoint(a, b)` |
+| `a + (b - a) * 0.5` | `std::midpoint(a, b)` |
+| `a + (b - a) * t` | `std::lerp(a, b, t)` |
+| `(1 - t) * a + t * b` | `std::lerp(a, b, t)` |
+
+## Changes in numerical behavior
+
+Replacing integer `(a + b) / 2` can change rounding. Integer division truncates
+toward zero, whereas `std::midpoint(a, b)` rounds toward `a`. For example,
+`(2 + 1) / 2` yields `1`, but `std::midpoint(2, 1)` yields `2`.
+Unsigned difference formulas can also change results when the subtraction wraps.
+
+Floating-point replacements can change rounding, overflow handling, signed zero,
+and results involving infinities or NaNs. These replacements adopt the numerical
+behavior of the standard library facilities; they do not promise identical
+results to the original arithmetic.
+
+## Options
+
+### IncludeStyle
+
+A string specifying which include ordering convention to use: `llvm` or
+`google`. The default is `llvm`.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/std/cmath b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/std/cmath
new file mode 100644
index 0000000000000..974dbdabf9107
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/std/cmath
@@ -0,0 +1,10 @@
+#ifndef CLANG_TIDY_TEST_STD_CMATH
+#define CLANG_TIDY_TEST_STD_CMATH
+namespace std {
+constexpr float lerp(float a, float b, float t) { return a + (b - a) * t; }
+constexpr double lerp(double a, double b, double t) { return a + (b - a) * t; }
+constexpr long double lerp(long double a, long double b, long double t) {
+  return a + (b - a) * t;
+}
+} // namespace std
+#endif
diff --git a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/std/numeric b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/std/numeric
new file mode 100644
index 0000000000000..0aa4e1b83b16c
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/std/numeric
@@ -0,0 +1,6 @@
+#ifndef CLANG_TIDY_TEST_STD_NUMERIC
+#define CLANG_TIDY_TEST_STD_NUMERIC
+namespace std {
+template <class T> constexpr T midpoint(T a, T b) { return a + (b - a) / 2; }
+} // namespace std
+#endif
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/Inputs/use-std-interpolation.h b/clang-tools-extra/test/clang-tidy/checkers/modernize/Inputs/use-std-interpolation.h
new file mode 100644
index 0000000000000..191b73d0dc608
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/Inputs/use-std-interpolation.h
@@ -0,0 +1,19 @@
+#ifndef CLANG_TIDY_TEST_USE_STD_INTERPOLATION_H
+#define CLANG_TIDY_TEST_USE_STD_INTERPOLATION_H
+
+#include <type_traits>
+// CHECK-FIXES: #include <cmath>
+// CHECK-FIXES-NEXT: #include <numeric>
+// CHECK-FIXES-NEXT: #include <type_traits>
+
+inline double header_midpoint(double a, double b) {
+  return (a + b) / 2;
+  // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: return std::midpoint(a, b);
+}
+inline double header_interpolation(double a, double b, double t) {
+  return a + (b - a) * t;
+  // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: use 'std::lerp' instead of manual linear interpolation
+  // CHECK-FIXES: return std::lerp(a, b, t);
+}
+#endif
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-interpolation-header.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-interpolation-header.cpp
new file mode 100644
index 0000000000000..327ec07c2ffba
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-interpolation-header.cpp
@@ -0,0 +1,4 @@
+// RUN: %check_clang_tidy -std=c++20-or-later -check-header %S/Inputs/use-std-interpolation.h %s modernize-use-std-interpolation %t -- -header-filter=.* -format-style=llvm
+
+#include "use-std-interpolation.h"
+#include "use-std-interpolation.h"
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-interpolation.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-interpolation.cpp
new file mode 100644
index 0000000000000..43e65ac3e2fc7
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-interpolation.cpp
@@ -0,0 +1,569 @@
+// RUN: %check_clang_tidy -std=c++20-or-later -check-suffixes=,MISSING %s modernize-use-std-interpolation %t \
+// RUN:   -- -format-style="{BasedOnStyle: LLVM, IncludeBlocks: Regroup}"
+// RUN: %check_clang_tidy -std=c++20-or-later -check-suffixes=,PRESENT %s modernize-use-std-interpolation %t-present \
+// RUN:   -- -- -DINCLUDES_PRESENT
+// RUN: clang-tidy %s -checks=-*,modernize-use-std-interpolation -allow-no-checks -- -std=c++11 2>&1 | count 0
+// RUN: clang-tidy %s -checks=-*,modernize-use-std-interpolation -allow-no-checks -- -std=c++14 2>&1 | count 0
+// RUN: clang-tidy %s -checks=-*,modernize-use-std-interpolation -allow-no-checks -- -std=c++17 2>&1 | count 0
+// RUN: clang-tidy %s -checks=-*,modernize-use-std-interpolation -allow-no-checks -- -x c -std=c17 2>&1 | count 0
+
+// CHECK-FIXES-MISSING: #include <cmath>
+// CHECK-FIXES-MISSING-NEXT: #include <numeric>
+
+#if __cplusplus >= 202002L
+
+#ifdef INCLUDES_PRESENT
+#include <cmath>
+#include <numeric>
+#endif
+// CHECK-FIXES-PRESENT-NOT: #include
+// CHECK-FIXES-PRESENT: #ifdef INCLUDES_PRESENT
+// CHECK-FIXES-PRESENT-NEXT: #include <cmath>
+// CHECK-FIXES-PRESENT-NEXT: #include <numeric>
+// CHECK-FIXES-PRESENT-NEXT: #endif
+// CHECK-FIXES-PRESENT-NOT: #include
+
+void int_calculations(int a, int b) {
+  auto sum = (a + b) / 2;
+  // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto sum = std::midpoint(a, b);
+
+  auto reversed_sum = (b + a) / 2;
+  // CHECK-MESSAGES: :[[@LINE-1]]:23: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto reversed_sum = std::midpoint(b, a);
+
+  auto difference = a + (b - a) / 2;
+  // CHECK-MESSAGES: :[[@LINE-1]]:21: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto difference = std::midpoint(a, b);
+
+  auto reversed_addition = (b - a) / 2 + a;
+  // CHECK-MESSAGES: :[[@LINE-1]]:28: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto reversed_addition = std::midpoint(a, b);
+
+  auto parenthesized = ((a) + ((b))) / (2);
+  // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto parenthesized = std::midpoint(a, b);
+
+  auto parenthesized_difference = (a) + (((b) - (a)) / (2));
+  // CHECK-MESSAGES: :[[@LINE-1]]:35: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto parenthesized_difference = std::midpoint(a, b);
+
+  auto signed_boundaries = (-2147483647 - 1 + 2147483647) / 2;
+  // CHECK-MESSAGES: :[[@LINE-1]]:28: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto signed_boundaries = std::midpoint((-2147483647 - 1), 2147483647);
+
+  auto odd_sum = (2 + 1) / 2;
+  // CHECK-MESSAGES: :[[@LINE-1]]:18: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto odd_sum = std::midpoint(2, 1);
+}
+
+void unsigned_calculations(unsigned a, unsigned b) {
+  auto difference = a + (b - a) / 2U;
+  // CHECK-MESSAGES: :[[@LINE-1]]:21: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto difference = std::midpoint(a, b);
+
+  auto sum_unsigned = (a + b) / 2;
+  // CHECK-MESSAGES: :[[@LINE-1]]:23: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto sum_unsigned = std::midpoint(a, b);
+
+  auto boundaries = (0U + 4294967295U) / 2;
+  // CHECK-MESSAGES: :[[@LINE-1]]:21: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto boundaries = std::midpoint(0U, 4294967295U);
+}
+
+void long_calculations(long a, long b) {
+  auto sum_long = (a + b) / 2;
+  // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto sum_long = std::midpoint(a, b);
+}
+
+void unsigned_long_calculations(unsigned long a, unsigned long b) {
+  auto sum_unsigned_long = (a + b) / 2;
+  // CHECK-MESSAGES: :[[@LINE-1]]:28: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto sum_unsigned_long = std::midpoint(a, b);
+}
+
+void long_long_calculations(long long a, long long b) {
+  auto sum_long_long = (a + b) / 2;
+  // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto sum_long_long = std::midpoint(a, b);
+}
+
+void unsigned_long_long_calculations(unsigned long long a, unsigned long long b) {
+  auto sum_unsigned_long_long = (a + b) / 2;
+  // CHECK-MESSAGES: :[[@LINE-1]]:33: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto sum_unsigned_long_long = std::midpoint(a, b);
+}
+
+void float_calculations(float a, float b, float t) {
+  auto sum = (a + b) / 2;
+  // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto sum = std::midpoint(a, b);
+
+  auto floating_divisor = (a + b) / 2.0f;
+  // CHECK-MESSAGES: :[[@LINE-1]]:27: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto floating_divisor = std::midpoint(a, b);
+
+  auto half = (a + b) * 0.5f;
+  // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto half = std::midpoint(a, b);
+
+  auto half_first = 0.5f * (a + b);
+  // CHECK-MESSAGES: :[[@LINE-1]]:21: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto half_first = std::midpoint(a, b);
+
+  auto difference = a + (b - a) / 2;
+  // CHECK-MESSAGES: :[[@LINE-1]]:21: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto difference = std::midpoint(a, b);
+
+  auto half_difference = a + (b - a) * 0.5f;
+  // CHECK-MESSAGES: :[[@LINE-1]]:26: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto half_difference = std::midpoint(a, b);
+
+  auto half_difference_swapped = 0.5f * (b - a) + a;
+  // CHECK-MESSAGES: :[[@LINE-1]]:34: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto half_difference_swapped = std::midpoint(a, b);
+
+  auto lerp_difference = a + (b - a) * t;
+  // CHECK-MESSAGES: :[[@LINE-1]]:26: warning: use 'std::lerp' instead of manual linear interpolation
+  // CHECK-FIXES: auto lerp_difference = std::lerp(a, b, t);
+
+  auto lerp_product_swapped = a + t * (b - a);
+  // CHECK-MESSAGES: :[[@LINE-1]]:31: warning: use 'std::lerp' instead of manual linear interpolation
+  // CHECK-FIXES: auto lerp_product_swapped = std::lerp(a, b, t);
+
+  auto lerp_addition_swapped = (b - a) * t + a;
+  // CHECK-MESSAGES: :[[@LINE-1]]:32: warning: use 'std::lerp' instead of manual linear interpolation
+  // CHECK-FIXES: auto lerp_addition_swapped = std::lerp(a, b, t);
+
+  auto lerp_both_swapped = t * (b - a) + a;
+  // CHECK-MESSAGES: :[[@LINE-1]]:28: warning: use 'std::lerp' instead of manual linear interpolation
+  // CHECK-FIXES: auto lerp_both_swapped = std::lerp(a, b, t);
+
+  auto lerp_weighted = (1 - t) * a + t * b;
+  // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: use 'std::lerp' instead of manual linear interpolation
+  // CHECK-FIXES: auto lerp_weighted = std::lerp(a, b, t);
+
+  auto lerp_weighted_swapped = b * t + a * (1 - t);
+  // CHECK-MESSAGES: :[[@LINE-1]]:32: warning: use 'std::lerp' instead of manual linear interpolation
+  // CHECK-FIXES: auto lerp_weighted_swapped = std::lerp(a, b, t);
+
+  auto lerp_weighted_products = a * (1 - t) + b * t;
+  // CHECK-MESSAGES: :[[@LINE-1]]:33: warning: use 'std::lerp' instead of manual linear interpolation
+  // CHECK-FIXES: auto lerp_weighted_products = std::lerp(a, b, t);
+
+  auto lerp_weighted_reverse_sum = t * b + (1 - t) * a;
+  // CHECK-MESSAGES: :[[@LINE-1]]:36: warning: use 'std::lerp' instead of manual linear interpolation
+  // CHECK-FIXES: auto lerp_weighted_reverse_sum = std::lerp(a, b, t);
+}
+
+void double_calculations(double a, double b, double t) {
+  auto sum = (a + b) / 2;
+  // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto sum = std::midpoint(a, b);
+
+  auto floating_divisor = (a + b) / 2.0;
+  // CHECK-MESSAGES: :[[@LINE-1]]:27: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto floating_divisor = std::midpoint(a, b);
+
+  auto half = (a + b) * 0.5;
+  // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto half = std::midpoint(a, b);
+
+  auto half_first = 0.5 * (a + b);
+  // CHECK-MESSAGES: :[[@LINE-1]]:21: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto half_first = std::midpoint(a, b);
+
+  auto difference = a + (b - a) / 2;
+  // CHECK-MESSAGES: :[[@LINE-1]]:21: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto difference = std::midpoint(a, b);
+
+  auto half_difference = a + (b - a) * 0.5;
+  // CHECK-MESSAGES: :[[@LINE-1]]:26: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto half_difference = std::midpoint(a, b);
+
+  auto half_difference_swapped = 0.5 * (b - a) + a;
+  // CHECK-MESSAGES: :[[@LINE-1]]:34: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto half_difference_swapped = std::midpoint(a, b);
+
+  auto lerp_difference = a + (b - a) * t;
+  // CHECK-MESSAGES: :[[@LINE-1]]:26: warning: use 'std::lerp' instead of manual linear interpolation
+  // CHECK-FIXES: auto lerp_difference = std::lerp(a, b, t);
+
+  auto lerp_product_swapped = a + t * (b - a);
+  // CHECK-MESSAGES: :[[@LINE-1]]:31: warning: use 'std::lerp' instead of manual linear interpolation
+  // CHECK-FIXES: auto lerp_product_swapped = std::lerp(a, b, t);
+
+  auto lerp_addition_swapped = (b - a) * t + a;
+  // CHECK-MESSAGES: :[[@LINE-1]]:32: warning: use 'std::lerp' instead of manual linear interpolation
+  // CHECK-FIXES: auto lerp_addition_swapped = std::lerp(a, b, t);
+
+  auto lerp_both_swapped = t * (b - a) + a;
+  // CHECK-MESSAGES: :[[@LINE-1]]:28: warning: use 'std::lerp' instead of manual linear interpolation
+  // CHECK-FIXES: auto lerp_both_swapped = std::lerp(a, b, t);
+
+  auto lerp_weighted = (1 - t) * a + t * b;
+  // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: use 'std::lerp' instead of manual linear interpolation
+  // CHECK-FIXES: auto lerp_weighted = std::lerp(a, b, t);
+
+  auto lerp_weighted_swapped = b * t + a * (1 - t);
+  // CHECK-MESSAGES: :[[@LINE-1]]:32: warning: use 'std::lerp' instead of manual linear interpolation
+  // CHECK-FIXES: auto lerp_weighted_swapped = std::lerp(a, b, t);
+
+  auto lerp_weighted_products = a * (1 - t) + b * t;
+  // CHECK-MESSAGES: :[[@LINE-1]]:33: warning: use 'std::lerp' instead of manual linear interpolation
+  // CHECK-FIXES: auto lerp_weighted_products = std::lerp(a, b, t);
+
+  auto lerp_weighted_reverse_sum = t * b + (1 - t) * a;
+  // CHECK-MESSAGES: :[[@LINE-1]]:36: warning: use 'std::lerp' instead of manual linear interpolation
+  // CHECK-FIXES: auto lerp_weighted_reverse_sum = std::lerp(a, b, t);
+
+  auto signed_zero = (-0.0 + 0.0) / 2;
+  // CHECK-MESSAGES: :[[@LINE-1]]:22: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto signed_zero = std::midpoint((-0.0), 0.0);
+
+  auto subnormal_values = (1e-320 + 2e-320) / 2;
+  // CHECK-MESSAGES: :[[@LINE-1]]:27: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto subnormal_values = std::midpoint(1e-320, 2e-320);
+}
+
+void long_double_calculations(long double a, long double b, long double t) {
+  auto sum = (a + b) / 2;
+  // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto sum = std::midpoint(a, b);
+
+  auto floating_divisor = (a + b) / 2.0L;
+  // CHECK-MESSAGES: :[[@LINE-1]]:27: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto floating_divisor = std::midpoint(a, b);
+
+  auto half = (a + b) * 0.5L;
+  // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto half = std::midpoint(a, b);
+
+  auto half_first = 0.5L * (a + b);
+  // CHECK-MESSAGES: :[[@LINE-1]]:21: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto half_first = std::midpoint(a, b);
+
+  auto difference = a + (b - a) / 2;
+  // CHECK-MESSAGES: :[[@LINE-1]]:21: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto difference = std::midpoint(a, b);
+
+  auto half_difference = a + (b - a) * 0.5L;
+  // CHECK-MESSAGES: :[[@LINE-1]]:26: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto half_difference = std::midpoint(a, b);
+
+  auto half_difference_swapped = 0.5L * (b - a) + a;
+  // CHECK-MESSAGES: :[[@LINE-1]]:34: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto half_difference_swapped = std::midpoint(a, b);
+
+  auto lerp_difference = a + (b - a) * t;
+  // CHECK-MESSAGES: :[[@LINE-1]]:26: warning: use 'std::lerp' instead of manual linear interpolation
+  // CHECK-FIXES: auto lerp_difference = std::lerp(a, b, t);
+
+  auto lerp_product_swapped = a + t * (b - a);
+  // CHECK-MESSAGES: :[[@LINE-1]]:31: warning: use 'std::lerp' instead of manual linear interpolation
+  // CHECK-FIXES: auto lerp_product_swapped = std::lerp(a, b, t);
+
+  auto lerp_addition_swapped = (b - a) * t + a;
+  // CHECK-MESSAGES: :[[@LINE-1]]:32: warning: use 'std::lerp' instead of manual linear interpolation
+  // CHECK-FIXES: auto lerp_addition_swapped = std::lerp(a, b, t);
+
+  auto lerp_both_swapped = t * (b - a) + a;
+  // CHECK-MESSAGES: :[[@LINE-1]]:28: warning: use 'std::lerp' instead of manual linear interpolation
+  // CHECK-FIXES: auto lerp_both_swapped = std::lerp(a, b, t);
+
+  auto lerp_weighted = (1 - t) * a + t * b;
+  // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: use 'std::lerp' instead of manual linear interpolation
+  // CHECK-FIXES: auto lerp_weighted = std::lerp(a, b, t);
+
+  auto lerp_weighted_swapped = b * t + a * (1 - t);
+  // CHECK-MESSAGES: :[[@LINE-1]]:32: warning: use 'std::lerp' instead of manual linear interpolation
+  // CHECK-FIXES: auto lerp_weighted_swapped = std::lerp(a, b, t);
+
+  auto lerp_weighted_products = a * (1 - t) + b * t;
+  // CHECK-MESSAGES: :[[@LINE-1]]:33: warning: use 'std::lerp' instead of manual linear interpolation
+  // CHECK-FIXES: auto lerp_weighted_products = std::lerp(a, b, t);
+
+  auto lerp_weighted_reverse_sum = t * b + (1 - t) * a;
+  // CHECK-MESSAGES: :[[@LINE-1]]:36: warning: use 'std::lerp' instead of manual linear interpolation
+  // CHECK-FIXES: auto lerp_weighted_reverse_sum = std::lerp(a, b, t);
+}
+using Real = double;
+typedef int Integer;
+
+auto aliases(const Real &a, Real &b, const Real &t) {
+  return a + (b - a) * t;
+  // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: use 'std::lerp' instead of manual linear interpolation
+  // CHECK-FIXES: return std::lerp(a, b, t);
+}
+
+auto typedef_midpoint(const Integer &a, Integer &&b) {
+  return a + (b - a) / 2;
+  // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: return std::midpoint(a, b);
+}
+
+auto explicit_casts(int a, int b) {
+  return (static_cast<double>(a) + static_cast<double>(b)) / 2;
+  // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: return std::midpoint(static_cast<double>(a), static_cast<double>(b));
+}
+
+constexpr int constexpr_midpoint(int a, int b) {
+  return a + (b - a) / 2;
+  // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: return std::midpoint(a, b);
+}
+
+struct Base { double value; };
+struct Derived : Base {};
+
+auto inherited_members(const Base &a, const Derived &b, double t) {
+  return a.value + (b.value - a.value) * t;
+  // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: use 'std::lerp' instead of manual linear interpolation
+  // CHECK-FIXES: return std::lerp(a.value, b.value, t);
+}
+
+auto array_elements(double *values, double t) {
+  return values[0] + (values[1] - values[0]) * t;
+  // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: use 'std::lerp' instead of manual linear interpolation
+  // CHECK-FIXES: return std::lerp(values[0], values[1], t);
+}
+
+namespace geometry {
+using Scalar = double;
+
+auto namespaced(Scalar a, Scalar b, Scalar t) {
+  return a + (b - a) * t;
+  // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: use 'std::lerp' instead of manual linear interpolation
+  // CHECK-FIXES: return std::lerp(a, b, t);
+}
+
+} // namespace geometry
+
+template <class Tag>
+
+auto nondependent_template(int a, int b) {
+  return (a + b) / 2;
+  // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: return std::midpoint(a, b);
+}
+
+template auto nondependent_template<int>(int, int);
+template auto nondependent_template<double>(int, int);
+
+template <class T> auto specialized(T a, T b) { return a + (b - a) / 2; }
+template <>
+auto specialized<double>(double a, double b) {
+  return a + (b - a) / 2;
+  // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: return std::midpoint(a, b);
+}
+
+auto captured_lambda(int a, int b) {
+  return [=] {
+    return (a + b) / 2;
+    // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: use 'std::midpoint' instead of manual midpoint calculation
+    // CHECK-FIXES: return std::midpoint(a, b);
+  };
+}
+
+auto lambda_parameters() {
+  return [](double a, double b, double t) {
+    return a + (b - a) * t;
+    // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: use 'std::lerp' instead of manual linear interpolation
+    // CHECK-FIXES: return std::lerp(a, b, t);
+  };
+}
+
+auto nested_calculations(int a, int b, int c) {
+  return ((a + b) / 2 + c) / 2;
+  // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: return (std::midpoint(a, b) + c) / 2;
+}
+
+double read_value();
+struct Convertible { operator double() const; };
+struct Number {
+  Number operator+(Number) const;
+  Number operator-(Number) const;
+  Number operator*(double) const;
+  Number operator/(int) const;
+};
+enum Enumeration { First, Second };
+
+void unrecognized_formulas(int i, int j, double a, double b, double t) {
+  (void)((i + j) / 3);
+  (void)((i - j) / 2);
+  (void)(i + (j - i) / 3);
+  (void)((1 - t) * a + (t + 1) * b);
+  (void)((2 - t) * a + t * b);
+  (void)(a + (b - t) * t);
+  (void)((a + b) * 0.25);
+}
+
+void mixed_midpoint_types(int i, int j, unsigned u, double a, float f, float g) {
+  (void)(i + (j - u) / 2);
+  (void)((i + u) / 2);
+  (void)((f + g) / 2.0);
+  (void)((i + j) / 2.0);
+  (void)((a + i) / 2);
+}
+
+void promoted_or_unsupported_types(short a, short b, char character,
+                                   bool flag, Enumeration value) {
+  (void)((a + b) / 2);
+  (void)((character + character) / 2);
+  (void)((flag + flag) / 2);
+  (void)((value + value) / 2);
+}
+
+void mixed_interpolation_types(int i, int j, double a, double b, double t,
+                               float f, float g) {
+  (void)(i + (j - i) * t);
+  (void)(a + (b - a) * i);
+  (void)(f + (g - f) * t);
+  (void)(a + (b - a) * 0.25f);
+}
+
+void overloaded_arithmetic(Number a, Number b, double t) {
+  (void)((a + b) / 2);
+  (void)(a + (b - a) * t);
+}
+
+void user_defined_conversion(Convertible a, double b) {
+  (void)((a + b) / 2);
+}
+
+void side_effects(double a, double b, double t) {
+  (void)((read_value() + b) / 2);
+  (void)(read_value() + (b - read_value()) * t);
+  (void)((1 - read_value()) * a + read_value() * b);
+  (void)((a++ + b) / 2);
+  (void)((++a + b) / 2);
+  (void)(a + (b - a) * (t = 0.5));
+}
+
+void volatile_reads(volatile double &a, double b, double t) {
+  (void)((a + b) / 2);
+  (void)(a + (b - a) * t);
+  (void)(b + (t - b) * a);
+}
+
+void unevaluated_contexts(double a, double b, double t) {
+  (void)sizeof((a + b) / 2);
+  (void)noexcept(a + (b - a) * t);
+  using Result = decltype((a + b) / 2);
+  (void)requires { (a + b) / 2; a + (b - a) * t; };
+}
+
+void named_constants(int a, int b) {
+  constexpr int Divisor = 2;
+  (void)((a + b) / Divisor);
+}
+
+template <class T> auto dependent(T a, T b, T t) {
+  return a + (b - a) * t;
+}
+template auto dependent(double, double, double);
+template auto dependent(float, float, float);
+template <class T> auto forwarding(T &&a, T &&b) {
+  return a + (b - a) / 2;
+}
+template <class... Ts> auto variadic(Ts... values) {
+  return (((values + values) / 2) + ...);
+}
+template <class T> concept Interpolatable = requires(T a, T b, T t) {
+  a + (b - a) * t;
+};
+auto generic_lambda() {
+  return [](auto a, auto b) { return (a + b) / 2; };
+}
+
+#define MIDPOINT(a, b) (((a) + (b)) / 2)
+#define TWO 2
+#define FIRST a
+#define ADD +
+#define IDENTITY(value) (value)
+void macro_cases(int a, int b) {
+  (void)MIDPOINT(a, b);
+  (void)((a + b) / TWO);
+  (void)((FIRST + b) / 2);
+  (void)((a ADD b) / 2);
+  (void)IDENTITY((a + b) / 2);
+}
+
+namespace associated {
+struct Number {};
+Number operator+(Number, Number);
+Number operator/(Number, int);
+void adl(Number a, Number b) { (void)((a + b) / 2); }
+} // namespace associated
+
+int intentionally_truncated(int a, int b) {
+  return (a + b) / 2; // NOLINT(modernize-use-std-interpolation)
+}
+
+auto comma_operand(int a, int b, int c) {
+  return ((static_cast<void>(a), b) + c) / 2;
+  // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: return std::midpoint((static_cast<void>(a), b), c);
+}
+
+auto conditional_operand(bool choose, int a, int b, int c) {
+  return ((choose ? a : b) + c) / 2;
+  // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: return std::midpoint((choose ? a : b), c);
+}
+
+auto unmatched_outer(int i, int j) {
+  return i + (j + i) / 2;
+  // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: return i + std::midpoint(j, i);
+}
+
+template auto forwarding<int &>(int &, int &);
+template auto variadic(int, int);
+
+auto instantiate_generic_lambda() {
+  return generic_lambda()(1, 2);
+}
+
+// These operations are side-effect-free, so their exclusions must work
+// independently of the side-effect guard.
+struct PureConvertible {
+  [[gnu::const]] operator double() const;
+};
+struct PureArithmetic {
+  [[gnu::const]] double operator+(double) const;
+};
+void pure_user_defined_operations(PureConvertible converted,
+                                  PureArithmetic overloaded,
+                                  double a, double b) {
+  (void)((converted + b) / 2);
+  (void)((static_cast<double>(converted) + b) / 2);
+  (void)((overloaded + a + b) / 2);
+}
+
+using geometry::Scalar;
+auto using_declaration(Scalar a, Scalar b, Scalar t) {
+  return a + (b - a) * t;
+  // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: use 'std::lerp' instead of manual linear interpolation
+  // CHECK-FIXES: return std::lerp(a, b, t);
+}
+
+template <Interpolatable T>
+auto constrained_concrete(T, int a, int b) {
+  return (a + b) / 2;
+  // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: return std::midpoint(a, b);
+}
+template auto constrained_concrete(double, int, int);
+
+void consume(int);
+void consume(short);
+void promotion_overload(short a, short b) {
+  consume((a + b) / 2);
+}
+
+#endif // __cplusplus >= 202002L

>From 1ae820ac88cf8f4c4f833943f2c59596aae745f3 Mon Sep 17 00:00:00 2001
From: Daniil Dudkin <unterumarmung at yandex.ru>
Date: Fri, 2 Oct 2026 00:45:02 +0300
Subject: [PATCH 2/3] [clang-tidy] Fix use-std-interpolation matching of long
 double literals

---
 .../modernize/UseStdInterpolationCheck.cpp        | 10 +++++++---
 .../checkers/modernize/use-std-interpolation.cpp  | 15 +++++++++++++++
 2 files changed, 22 insertions(+), 3 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/modernize/UseStdInterpolationCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStdInterpolationCheck.cpp
index 5febc5835b8ff..b5fe4c5905486 100644
--- a/clang-tools-extra/clang-tidy/modernize/UseStdInterpolationCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/UseStdInterpolationCheck.cpp
@@ -23,6 +23,10 @@ AST_MATCHER(Expr, hasSideEffects) {
   return Node.HasSideEffects(Finder->getASTContext());
 }
 
+AST_MATCHER_P(FloatingLiteral, hasExactValue, double, Value) {
+  return Node.getValue().isExactlyValue(Value);
+}
+
 AST_MATCHER(Expr, isMacroExpanded) {
   if (Node.getBeginLoc().isMacroID() || Node.getEndLoc().isMacroID())
     return true;
@@ -72,10 +76,10 @@ static InterpolationMatchers makeInterpolationMatchers() {
   const auto RepeatedFactor = ignoringParenImpCasts(
       expr(matchers::isStatementIdenticalToBoundNode("factor")));
   const auto Two = ignoringParenImpCasts(
-      expr(anyOf(integerLiteral(equals(2)), floatLiteral(equals(2.0)))));
-  const auto Half = ignoringParenImpCasts(floatLiteral(equals(0.5)));
+      expr(anyOf(integerLiteral(equals(2)), floatLiteral(hasExactValue(2.0)))));
+  const auto Half = ignoringParenImpCasts(floatLiteral(hasExactValue(0.5)));
   const auto One = ignoringParenImpCasts(
-      expr(anyOf(integerLiteral(equals(1)), floatLiteral(equals(1.0)))));
+      expr(anyOf(integerLiteral(equals(1)), floatLiteral(hasExactValue(1.0)))));
 
   // Midpoints: (a + b) / 2 and a + (b - a) / 2, also using * 0.5.
   const auto Sum = ignoringParenImpCasts(binaryOperator(
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-interpolation.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-interpolation.cpp
index 43e65ac3e2fc7..a97dcfd91b594 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-interpolation.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-interpolation.cpp
@@ -2,6 +2,10 @@
 // RUN:   -- -format-style="{BasedOnStyle: LLVM, IncludeBlocks: Regroup}"
 // RUN: %check_clang_tidy -std=c++20-or-later -check-suffixes=,PRESENT %s modernize-use-std-interpolation %t-present \
 // RUN:   -- -- -DINCLUDES_PRESENT
+// RUN: %check_clang_tidy -std=c++20-or-later -check-suffixes=,MISSING %s modernize-use-std-interpolation %t-x86 \
+// RUN:   -- -format-style="{BasedOnStyle: LLVM, IncludeBlocks: Regroup}" -- -target x86_64-unknown-linux-gnu
+// RUN: %check_clang_tidy -std=c++20-or-later -check-suffixes=,MISSING %s modernize-use-std-interpolation %t-aarch64 \
+// RUN:   -- -format-style="{BasedOnStyle: LLVM, IncludeBlocks: Regroup}" -- -target aarch64-unknown-linux-gnu
 // RUN: clang-tidy %s -checks=-*,modernize-use-std-interpolation -allow-no-checks -- -std=c++11 2>&1 | count 0
 // RUN: clang-tidy %s -checks=-*,modernize-use-std-interpolation -allow-no-checks -- -std=c++14 2>&1 | count 0
 // RUN: clang-tidy %s -checks=-*,modernize-use-std-interpolation -allow-no-checks -- -std=c++17 2>&1 | count 0
@@ -287,7 +291,18 @@ void long_double_calculations(long double a, long double b, long double t) {
   auto lerp_weighted_reverse_sum = t * b + (1 - t) * a;
   // CHECK-MESSAGES: :[[@LINE-1]]:36: warning: use 'std::lerp' instead of manual linear interpolation
   // CHECK-FIXES: auto lerp_weighted_reverse_sum = std::lerp(a, b, t);
+
+  auto floating_complement = (1.0L - t) * a + t * b;
+  // CHECK-MESSAGES: :[[@LINE-1]]:30: warning: use 'std::lerp' instead of manual linear interpolation
+  // CHECK-FIXES: auto floating_complement = std::lerp(a, b, t);
+}
+
+void long_double_near_misses(long double a, long double b, long double t) {
+  (void)((a + b) / 2.01L);
+  (void)((a + b) * 0.51L);
+  (void)((1.01L - t) * a + t * b);
 }
+
 using Real = double;
 typedef int Integer;
 

>From 9b77e90e0c6ce13e06d9da6aeacee6244d2b9338 Mon Sep 17 00:00:00 2001
From: Daniil Dudkin <unterumarmung at yandex.ru>
Date: Fri, 2 Oct 2026 00:47:36 +0300
Subject: [PATCH 3/3] [clang-tidy] Avoid use-std-interpolation false positives
 for additive chains

---
 .../modernize/UseStdInterpolationCheck.cpp    |  7 ++-
 .../checks/modernize/use-std-interpolation.md |  4 ++
 .../modernize/use-std-interpolation.cpp       | 63 ++++++++++++++++++-
 3 files changed, 72 insertions(+), 2 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/modernize/UseStdInterpolationCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStdInterpolationCheck.cpp
index b5fe4c5905486..b30e3b39e73b4 100644
--- a/clang-tools-extra/clang-tidy/modernize/UseStdInterpolationCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/UseStdInterpolationCheck.cpp
@@ -82,8 +82,13 @@ static InterpolationMatchers makeInterpolationMatchers() {
       expr(anyOf(integerLiteral(equals(1)), floatLiteral(hasExactValue(1.0)))));
 
   // Midpoints: (a + b) / 2 and a + (b - a) / 2, also using * 0.5.
+  // Preserve parentheses around endpoints so that an ungrouped chain such as
+  // (a + b + 1) / 2 is not treated as the midpoint of a + b and 1.
+  const auto GroupedEndpoint =
+      unless(ignoringImpCasts(binaryOperator(hasAnyOperatorName("+", "-"))));
   const auto Sum = ignoringParenImpCasts(binaryOperator(
-      hasOperatorName("+"), SameType, hasLHS(Start), hasRHS(End)));
+      hasOperatorName("+"), SameType, hasLHS(expr(GroupedEndpoint, Start)),
+      hasRHS(expr(GroupedEndpoint, End))));
   const auto Difference = ignoringParenImpCasts(binaryOperator(
       hasOperatorName("-"), SameType, hasLHS(End), hasRHS(RepeatedStart)));
   const auto HalfSum = binaryOperator(
diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-interpolation.md b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-interpolation.md
index f639e64dbaae7..09ae89554d1d3 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-interpolation.md
+++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-std-interpolation.md
@@ -47,6 +47,10 @@ The following expressions are recognized:
 | `a + (b - a) * t` | `std::lerp(a, b, t)` |
 | `(1 - t) * a + t * b` | `std::lerp(a, b, t)` |
 
+Sum midpoint formulas must have two endpoints. Ungrouped addition or subtraction
+chains such as `(a + b + 1) / 2` are excluded. Parenthesized endpoints such as
+`((a + b) + c) / 2` are supported.
+
 ## Changes in numerical behavior
 
 Replacing integer `(a + b) / 2` can change rounding. Integer division truncates
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-interpolation.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-interpolation.cpp
index a97dcfd91b594..08cd9e4a0aa0b 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-interpolation.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-std-interpolation.cpp
@@ -52,7 +52,7 @@ void int_calculations(int a, int b) {
   // CHECK-MESSAGES: :[[@LINE-1]]:35: warning: use 'std::midpoint' instead of manual midpoint calculation
   // CHECK-FIXES: auto parenthesized_difference = std::midpoint(a, b);
 
-  auto signed_boundaries = (-2147483647 - 1 + 2147483647) / 2;
+  auto signed_boundaries = ((-2147483647 - 1) + 2147483647) / 2;
   // CHECK-MESSAGES: :[[@LINE-1]]:28: warning: use 'std::midpoint' instead of manual midpoint calculation
   // CHECK-FIXES: auto signed_boundaries = std::midpoint((-2147483647 - 1), 2147483647);
 
@@ -397,6 +397,34 @@ auto nested_calculations(int a, int b, int c) {
   // CHECK-FIXES: return (std::midpoint(a, b) + c) / 2;
 }
 
+void grouped_endpoints(int a, int b, int c) {
+  auto left_sum = ((a + b) + c) / 2;
+  // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto left_sum = std::midpoint((a + b), c);
+
+  auto right_sum = (a + (b + c)) / 2;
+  // CHECK-MESSAGES: :[[@LINE-1]]:20: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto right_sum = std::midpoint(a, (b + c));
+
+  auto left_difference = ((a - b) + c) / 2;
+  // CHECK-MESSAGES: :[[@LINE-1]]:26: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto left_difference = std::midpoint((a - b), c);
+
+  auto right_difference = (a + (b - c)) / 2;
+  // CHECK-MESSAGES: :[[@LINE-1]]:27: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto right_difference = std::midpoint(a, (b - c));
+}
+
+void floating_grouped_endpoints(double a, double b, double c) {
+  auto sum = ((a + b) + c) * 0.5;
+  // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto sum = std::midpoint((a + b), c);
+
+  auto reversed_product = 0.5 * (a + (b - c));
+  // CHECK-MESSAGES: :[[@LINE-1]]:27: warning: use 'std::midpoint' instead of manual midpoint calculation
+  // CHECK-FIXES: auto reversed_product = std::midpoint(a, (b - c));
+}
+
 double read_value();
 struct Convertible { operator double() const; };
 struct Number {
@@ -417,6 +445,39 @@ void unrecognized_formulas(int i, int j, double a, double b, double t) {
   (void)((a + b) * 0.25);
 }
 
+void additive_chains(int foldLen, int sinceLast, int other) {
+  (void)((foldLen + sinceLast + 1) / 2);
+  (void)((1 + foldLen + sinceLast) / 2);
+  (void)((foldLen + sinceLast + other) / 2);
+  (void)((foldLen - sinceLast + 1) / 2);
+  (void)((foldLen + sinceLast - other + 1) / 2);
+  (void)((((foldLen + sinceLast + 1))) / 2);
+}
+
+void unsigned_additive_chains(unsigned a, unsigned b) {
+  (void)((a + b + 1U) / 2U);
+  (void)((a - b + 1U) / 2U);
+}
+
+void floating_additive_chains(double a, double b, double c) {
+  (void)((a + b + 1.0) / 2.0);
+  (void)((a + b + c) * 0.5);
+  (void)(0.5 * (a + b + c));
+  (void)((a - b + c) * 0.5);
+}
+
+void long_double_additive_chains(long double a, long double b) {
+  (void)((a + b + 1.0L) / 2.0L);
+  (void)((a + b + 1.0L) * 0.5L);
+}
+
+template <class T> int template_additive_chain(int a, int b) {
+  return (a + b + 1) / 2;
+}
+template int template_additive_chain<void>(int, int);
+
+auto additive_chain_lambda = [](int a, int b) { return (a + b + 1) / 2; };
+
 void mixed_midpoint_types(int i, int j, unsigned u, double a, float f, float g) {
   (void)(i + (j - u) / 2);
   (void)((i + u) / 2);



More information about the cfe-commits mailing list