Extract a few functions for reuse in inference pipelines

Also create isSupportedPointer() instead of inlining it in yet another place.
Haven't migrating current (should-be) callers, though.

PiperOrigin-RevId: 551859905
Change-Id: I8d314e46a2cc6ccfadb1b1804e05d14ffc05defc
diff --git a/nullability/BUILD b/nullability/BUILD
index 5a6b904..ae4b293 100644
--- a/nullability/BUILD
+++ b/nullability/BUILD
@@ -142,6 +142,7 @@
     deps = [
         ":type_nullability",
         "@absl//absl/log:check",
+        "@llvm-project//clang:ast",
         "@llvm-project//clang:basic",
         "@llvm-project//clang:testing",
         "@llvm-project//llvm:Support",
diff --git a/nullability/inference/BUILD b/nullability/inference/BUILD
index f15a4da..a61b978 100644
--- a/nullability/inference/BUILD
+++ b/nullability/inference/BUILD
@@ -10,6 +10,7 @@
     hdrs = ["collect_evidence.h"],
     deps = [
         ":inference_cc_proto",
+        ":inferrable",
         "//nullability:pointer_nullability",
         "//nullability:pointer_nullability_analysis",
         "//nullability:pointer_nullability_lattice",
@@ -67,6 +68,34 @@
 )
 
 cc_library(
+    name = "inferrable",
+    srcs = ["inferrable.cc"],
+    hdrs = ["inferrable.h"],
+    deps = [
+        "//nullability:type_nullability",
+        "@llvm-project//clang:ast",
+        "@llvm-project//clang:basic",
+    ],
+)
+
+cc_test(
+    name = "inferrable_test",
+    srcs = ["inferrable_test.cc"],
+    deps = [
+        ":inference_cc_proto",
+        ":inferrable",
+        "@llvm-project//clang:ast",
+        "@llvm-project//clang:basic",
+        "@llvm-project//clang:testing",
+        "@llvm-project//clang/unittests:dataflow_testing_support",
+        "@llvm-project//llvm:Support",
+        "@llvm-project//third-party/unittest:gmock",
+        "@llvm-project//third-party/unittest:gtest",
+        "@llvm-project//third-party/unittest:gtest_main",
+    ],
+)
+
+cc_library(
     name = "infer_tu",
     srcs = ["infer_tu.cc"],
     hdrs = ["infer_tu.h"],
diff --git a/nullability/inference/collect_evidence.cc b/nullability/inference/collect_evidence.cc
index 673067b..6cd8165 100644
--- a/nullability/inference/collect_evidence.cc
+++ b/nullability/inference/collect_evidence.cc
@@ -10,6 +10,7 @@
 #include <vector>
 
 #include "nullability/inference/inference.proto.h"
+#include "nullability/inference/inferrable.h"
 #include "nullability/pointer_nullability.h"
 #include "nullability/pointer_nullability_analysis.h"
 #include "nullability/pointer_nullability_lattice.h"
@@ -87,21 +88,6 @@
 }
 
 namespace {
-bool isInferenceTarget(const FunctionDecl &FD) {
-  return
-      // Function templates are in principle inferrable.
-      // However since we don't analyze their bodies, and other implementations
-      // cannot interact with them directly, we can't perform any nontrivial
-      // inference, just propagate annotations across redecls.
-      // For now, we don't do this as some infra (NullabilityWalker) doesn't
-      // work on dependent code.
-      !FD.isDependentContext() &&
-      // Inferring properties of template instantiations isn't useful in
-      // itself. We can't record them anywhere unless they apply to the
-      // template in general.
-      // TODO: work out in what circumstances that would be safe.
-      !FD.getTemplateInstantiationPattern();
-}
 
 void collectEvidenceFromDereference(
     std::vector<std::pair<PointerTypeNullability, Slot>> &InferrableSlots,
diff --git a/nullability/inference/inferrable.cc b/nullability/inference/inferrable.cc
new file mode 100644
index 0000000..2adb12a
--- /dev/null
+++ b/nullability/inference/inferrable.cc
@@ -0,0 +1,52 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "nullability/inference/inferrable.h"
+
+#include "nullability/type_nullability.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclBase.h"
+#include "clang/AST/Type.h"
+#include "clang/Basic/LLVM.h"
+
+namespace clang::tidy::nullability {
+
+namespace {
+
+bool isInferrable(QualType T) {
+  return isSupportedPointer(T.getNonReferenceType().getCanonicalType());
+}
+
+}  // namespace
+
+int countInferrableSlots(const Decl& D) {
+  const clang::FunctionDecl* Func = dyn_cast<clang::FunctionDecl>(&D);
+  if (!Func) return 0;
+  int Slots = 0;
+  if (isInferrable(Func->getReturnType())) ++Slots;
+  for (auto* P : Func->parameters())
+    if (isInferrable(P->getType())) ++Slots;
+  return Slots;
+}
+
+bool isInferenceTarget(const Decl& D) {
+  // For now, only support inferring nullability of functions.
+  const auto* FD = dyn_cast<FunctionDecl>(&D);
+  if (!FD) return false;
+  return
+      // Function templates are in principle inferrable.
+      // However since we don't analyze their bodies, and other implementations
+      // cannot interact with them directly, we can't perform any nontrivial
+      // inference, just propagate annotations across redecls.
+      // For now, we don't do this as some infra (NullabilityWalker) doesn't
+      // work on dependent code.
+      !FD->isDependentContext() &&
+      // Inferring properties of template instantiations isn't useful in
+      // itself. We can't record them anywhere unless they apply to the
+      // template in general.
+      // TODO: work out in what circumstances that would be safe.
+      !FD->getTemplateInstantiationPattern();
+}
+
+}  // namespace clang::tidy::nullability
diff --git a/nullability/inference/inferrable.h b/nullability/inference/inferrable.h
new file mode 100644
index 0000000..552fe6a
--- /dev/null
+++ b/nullability/inference/inferrable.h
@@ -0,0 +1,23 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef CRUBIT_NULLABILITY_INFERENCE_INFERRABLE_H_
+#define CRUBIT_NULLABILITY_INFERENCE_INFERRABLE_H_
+
+#include "clang/AST/DeclBase.h"
+
+namespace clang::tidy::nullability {
+
+/// Should we attempt to deduce nullability for this symbol?
+bool isInferenceTarget(const Decl &);
+
+/// The number of nullability slots in this symbol's type which can be inferred.
+///
+/// This may not be all the slots in the type: e.g. `int** X` has outer and
+/// inner nullability, we may support only inferring outer.
+int countInferrableSlots(const clang::Decl &);
+
+}  // namespace clang::tidy::nullability
+
+#endif  // THIRD_PARTY_CRUBIT_NULLABILITY_INFERENCE_INFERRABLE_H_
diff --git a/nullability/inference/inferrable_test.cc b/nullability/inference/inferrable_test.cc
new file mode 100644
index 0000000..e906203
--- /dev/null
+++ b/nullability/inference/inferrable_test.cc
@@ -0,0 +1,108 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "nullability/inference/inferrable.h"
+
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclBase.h"
+#include "clang/AST/DeclCXX.h"
+#include "clang/AST/Expr.h"
+#include "clang/Basic/LLVM.h"
+#include "clang/Testing/TestAST.h"
+#include "llvm/ADT/StringRef.h"
+#include "third_party/llvm/llvm-project/third-party/unittest/googletest/include/gtest/gtest.h"
+
+namespace clang::tidy::nullability {
+namespace {
+
+template <class T = NamedDecl>
+const T &lookup(llvm::StringRef Name, const DeclContext &DC) {
+  auto L = DC.lookup(&DC.getParentASTContext().Idents.get(Name));
+  EXPECT_TRUE(L.isSingleResult()) << Name;
+  auto *Result = L.find_first<T>();
+  EXPECT_NE(Result, nullptr) << Name;
+  return *Result;
+}
+
+TEST(InferrableTest, IsInferenceTarget) {
+  TestAST AST(R"cc(
+    int* Pointer;
+    int* func(int*, int**);
+    void empty() {}
+
+    class Cls {
+      void method();
+    };
+
+    template <int X>
+    void funcTmpl(int*) {}
+
+    auto& FuncTmplSpec = funcTmpl<2>;
+  )cc");
+
+  auto &TU = *AST.context().getTranslationUnitDecl();
+  EXPECT_FALSE(isInferenceTarget(lookup("Pointer", TU)));
+  EXPECT_TRUE(isInferenceTarget(lookup("func", TU)));
+  EXPECT_TRUE(isInferenceTarget(lookup("empty", TU)));
+
+  auto &Cls = lookup<CXXRecordDecl>("Cls", TU);
+  EXPECT_FALSE(isInferenceTarget(Cls));
+  EXPECT_TRUE(isInferenceTarget(lookup("method", Cls)));
+
+  // A function template is not an inference target.
+  const FunctionTemplateDecl &FuncTmpl =
+      lookup<FunctionTemplateDecl>("funcTmpl", TU);
+  EXPECT_FALSE(isInferenceTarget(FuncTmpl));
+  EXPECT_FALSE(isInferenceTarget(*FuncTmpl.getTemplatedDecl()));
+  // The template specialization is *also* not an inference target.
+  const VarDecl &FuncTmplSpec = lookup<VarDecl>("FuncTmplSpec", TU);
+  EXPECT_FALSE(isInferenceTarget(FuncTmplSpec));
+  const ValueDecl &FuncTmpl2 =
+      *cast<DeclRefExpr>(FuncTmplSpec.getInit()->IgnoreImplicit())->getDecl();
+  EXPECT_FALSE(isInferenceTarget(FuncTmpl2));
+}
+
+TEST(InferrableTest, CountInferrableSlots) {
+  TestAST AST(R"cc(
+    using Pointer = int *;
+    template <class T>
+    struct S;
+    struct T;
+
+    void f1(int *);
+    void f2(Pointer);
+    void f3(int **);
+    void f4(Pointer *);
+    void f5(int *&);
+    void f6(int (*)());  // function pointer
+
+    int *g1(int);
+    Pointer g2(int);
+
+    void h1(S<int *>);
+    void h2(int T::*);      // pointer to data member
+    void h3(int (T::*)());  // pointer to member function
+  )cc");
+  auto &TU = *AST.context().getTranslationUnitDecl();
+
+  // All the 'f's have a single pointer arg.
+  EXPECT_EQ(1, countInferrableSlots(lookup("f1", TU)));
+  EXPECT_EQ(1, countInferrableSlots(lookup("f2", TU)));
+  EXPECT_EQ(1, countInferrableSlots(lookup("f3", TU)));
+  EXPECT_EQ(1, countInferrableSlots(lookup("f4", TU)));
+  EXPECT_EQ(1, countInferrableSlots(lookup("f5", TU)));
+  EXPECT_EQ(1, countInferrableSlots(lookup("f6", TU)));
+
+  // All the 'g's have a pointer return.
+  EXPECT_EQ(1, countInferrableSlots(lookup("g1", TU)));
+  EXPECT_EQ(1, countInferrableSlots(lookup("g2", TU)));
+
+  // The 'h's have types that aren't really pointers.
+  EXPECT_EQ(0, countInferrableSlots(lookup("h1", TU)));
+  EXPECT_EQ(0, countInferrableSlots(lookup("h2", TU)));
+  EXPECT_EQ(0, countInferrableSlots(lookup("h3", TU)));
+}
+
+}  // namespace
+}  // namespace clang::tidy::nullability
diff --git a/nullability/type_nullability.cc b/nullability/type_nullability.cc
index ae6d6b4..c5c4f18 100644
--- a/nullability/type_nullability.cc
+++ b/nullability/type_nullability.cc
@@ -24,6 +24,8 @@
 
 namespace clang::tidy::nullability {
 
+bool isSupportedPointer(QualType T) { return llvm::isa<PointerType>(T); }
+
 PointerTypeNullability PointerTypeNullability::createSymbolic(
     dataflow::Arena &A) {
   PointerTypeNullability Symbolic;
diff --git a/nullability/type_nullability.h b/nullability/type_nullability.h
index 3c753dd..2a68815 100644
--- a/nullability/type_nullability.h
+++ b/nullability/type_nullability.h
@@ -32,6 +32,7 @@
 #include "absl/log/check.h"
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/Expr.h"
+#include "clang/AST/Type.h"
 #include "clang/Analysis/FlowSensitive/Arena.h"
 #include "clang/Analysis/FlowSensitive/Formula.h"
 #include "clang/Basic/LLVM.h"
@@ -39,6 +40,13 @@
 
 namespace clang::tidy::nullability {
 
+/// Is this exactly a pointer type that we track outer nullability for?
+/// Does not unwrap sugar, consider isSupportedPointer(T.getCanonicalType()).
+///
+/// (For now, only regular PointerTypes, in future we should consider supporting
+/// pointer-to-member, ObjC pointers, etc).
+bool isSupportedPointer(QualType);
+
 /// Describes the nullability contract of a pointer "slot" within a type.
 ///
 /// This may be concrete: nullable/non-null/unknown nullability.
diff --git a/nullability/type_nullability_test.cc b/nullability/type_nullability_test.cc
index f10f2fc..7306282 100644
--- a/nullability/type_nullability_test.cc
+++ b/nullability/type_nullability_test.cc
@@ -5,6 +5,7 @@
 #include "nullability/type_nullability.h"
 
 #include "absl/log/check.h"
+#include "clang/AST/Decl.h"
 #include "clang/Basic/Specifiers.h"
 #include "clang/Testing/TestAST.h"
 #include "llvm/ADT/StringRef.h"
@@ -15,6 +16,41 @@
 namespace {
 using testing::ElementsAre;
 
+TEST(TypeNullabilityTest, IsSupportedPointer) {
+  TestAST AST(R"cpp(
+    using NotPointer = int;
+    using Pointer = NotPointer*;
+    using FuncPointer = Pointer (*)(Pointer);
+    using SugaredPointer = Pointer;
+
+    struct S;
+    using PointerDataMember = Pointer S::*;
+    using PointerMemberFunction = Pointer (S::*)(Pointer);
+
+    @class X;
+    using ObjCPointer = X;
+
+    template <class>
+    struct Container;
+    using ContainsPointers = Container<int*>;
+  )cpp");
+
+  auto Underlying = [&](llvm::StringRef Name) {
+    auto Lookup = AST.context().getTranslationUnitDecl()->lookup(
+        &AST.context().Idents.get(Name));
+    EXPECT_TRUE(Lookup.isSingleResult());
+    return Lookup.find_first<TypeAliasDecl>()->getUnderlyingType();
+  };
+  EXPECT_FALSE(isSupportedPointer(Underlying("NotPointer")));
+  EXPECT_TRUE(isSupportedPointer(Underlying("Pointer")));
+  EXPECT_TRUE(isSupportedPointer(Underlying("FuncPointer")));
+  EXPECT_FALSE(isSupportedPointer(Underlying("SugaredPointer")));
+  EXPECT_FALSE(isSupportedPointer(Underlying("PointerDataMember")));
+  EXPECT_FALSE(isSupportedPointer(Underlying("PointerMemberFunction")));
+  EXPECT_FALSE(isSupportedPointer(Underlying("ObjCPointer")));
+  EXPECT_FALSE(isSupportedPointer(Underlying("ContainsPointers")));
+}
+
 class GetNullabilityAnnotationsFromTypeTest : public ::testing::Test {
  protected:
   // C++ declarations prepended before parsing type in nullVec().