For classes nested in template classes, include outer tparams in nullability
Given `template <class T> struct X { struct Y {}; };`
Y can depend on T in the same ways that X can.
Therefore the nullability of T is part of Y's nullability too.
For now, there's no observable effect except that the nullability vectors are the right length, padded with Unspecified, but we're building towards nested template support.
- when we walk up the redecl chain there is no sugar.
My other patch will provide this in some cases ("Resugar substituted type params found in class template instantiations"), we need to look up the template args in the instantiation context.
- we don't consume the outer class nullability anywhere. We will be able to do this around substituteNullabilityAnnotationsInClassTemplate, addressing TODOs about nested templates. This will require some restructuring as we don't actually whether the inner class is a template anymore.
PiperOrigin-RevId: 527969535
diff --git a/nullability_verification/pointer_nullability.cc b/nullability_verification/pointer_nullability.cc
index 42ff806..28b0e67 100644
--- a/nullability_verification/pointer_nullability.cc
+++ b/nullability_verification/pointer_nullability.cc
@@ -130,6 +130,15 @@
void Visit(QualType T) { Base::Visit(T.getTypePtr()); }
void Visit(const TemplateArgument& TA) {
if (TA.getKind() == TemplateArgument::Type) Visit(TA.getAsType());
+ if (TA.getKind() == TemplateArgument::Pack)
+ for (const auto& PackElt : TA.getPackAsArray()) Visit(PackElt);
+ }
+ void Visit(const DeclContext* DC) {
+ // For now, only consider enclosing classes.
+ // TODO: The nullability of template functions can affect local classes too,
+ // this can be relevant e.g. when instantiating templates with such types.
+ if (auto* CRD = llvm::dyn_cast<CXXRecordDecl>(DC))
+ Visit(DC->getParentASTContext().getRecordType(CRD));
}
void VisitType(const Type* T) {
@@ -162,7 +171,10 @@
return;
}
+ auto* CRD = TST->getAsCXXRecordDecl();
+ CHECK(CRD) << "Expected an alias or class specialization in concrete code";
ignoreUnexpectedNullability();
+ Visit(CRD->getDeclContext());
for (auto TA : TST->template_arguments()) Visit(TA);
}
@@ -193,7 +205,10 @@
void VisitRecordType(const RecordType* RT) {
ignoreUnexpectedNullability();
+ Visit(RT->getDecl()->getDeclContext());
if (auto* CTSD = dyn_cast<ClassTemplateSpecializationDecl>(RT->getDecl())) {
+ // TODO: if this is an instantiation, these args lack sugar.
+ // We can try to retrieve it from the current template context.
for (auto& TA : CTSD->getTemplateArgs().asArray()) Visit(TA);
}
}
@@ -216,23 +231,25 @@
Visit(PT->getPointeeType());
}
};
-} // namespace
-unsigned countPointersInType(QualType T) {
+template <typename T>
+unsigned countPointers(const T& Object) {
struct Walker : public NullabilityWalker<Walker> {
unsigned Count = 0;
void report(const PointerType*, NullabilityKind) { ++Count; }
- } PointerCountVisitor;
- PointerCountVisitor.Visit(T.getCanonicalType());
- return PointerCountVisitor.Count;
+ } PointerCountWalker;
+ PointerCountWalker.Visit(Object);
+ return PointerCountWalker.Count;
}
-unsigned countPointersInType(TemplateArgument TA) {
- if (TA.getKind() == TemplateArgument::Type) {
- return countPointersInType(TA.getAsType().getCanonicalType());
- }
- return 0;
+} // namespace
+
+unsigned countPointersInType(QualType T) { return countPointers(T); }
+
+unsigned countPointersInType(const DeclContext* DC) {
+ return countPointers(DC);
}
+unsigned countPointersInType(TemplateArgument TA) { return countPointers(TA); }
QualType exprType(const Expr* E) {
if (E->hasPlaceholderType(BuiltinType::BoundMember))
diff --git a/nullability_verification/pointer_nullability.h b/nullability_verification/pointer_nullability.h
index 58a7512..b2f4ae3 100644
--- a/nullability_verification/pointer_nullability.h
+++ b/nullability_verification/pointer_nullability.h
@@ -127,6 +127,7 @@
unsigned countPointersInType(QualType T);
unsigned countPointersInType(const Expr* E);
unsigned countPointersInType(TemplateArgument TA);
+unsigned countPointersInType(const DeclContext* DC);
QualType exprType(const Expr* E);
diff --git a/nullability_verification/pointer_nullability_analysis.cc b/nullability_verification/pointer_nullability_analysis.cc
index b5e878f..9479b84 100644
--- a/nullability_verification/pointer_nullability_analysis.cc
+++ b/nullability_verification/pointer_nullability_analysis.cc
@@ -127,7 +127,8 @@
unsigned ArgIndex = ST->getIndex();
auto TemplateArgs = Specialization->getTemplateArgs().asArray();
- unsigned PointerCount = 0;
+ unsigned PointerCount =
+ countPointersInType(Specialization->getDeclContext());
for (auto TA : TemplateArgs.take_front(ArgIndex)) {
PointerCount += countPointersInType(TA);
}
diff --git a/nullability_verification/pointer_nullability_test.cc b/nullability_verification/pointer_nullability_test.cc
index 507f4e9..5f59121 100644
--- a/nullability_verification/pointer_nullability_test.cc
+++ b/nullability_verification/pointer_nullability_test.cc
@@ -119,10 +119,11 @@
struct Outer {
struct Inner;
};
+ using OuterNullableInner = Outer<int* _Nonnull>::Inner;
)cpp";
// TODO: should be [NonNull]
- // We don't include parent template params in class nullability yet.
- EXPECT_THAT(nullVec("Outer<int* _Nonnull>::Inner"), ElementsAre());
+ EXPECT_THAT(nullVec("Outer<int* _Nonnull>::Inner"),
+ ElementsAre(NullabilityKind::Unspecified));
}
TEST_F(GetNullabilityAnnotationsFromTypeTest, ReferenceOuterTemplateParam) {
@@ -219,11 +220,13 @@
};
)cpp";
// TODO: should be [Unspecified, Nonnull]
- EXPECT_THAT(nullVec("TupleWrapper<int*, int* _Nonnull>::Tuple"),
- ElementsAre());
+ EXPECT_THAT(
+ nullVec("TupleWrapper<int*, int* _Nonnull>::Tuple"),
+ ElementsAre(NullabilityKind::Unspecified, NullabilityKind::Unspecified));
// TODO: should be [Nullable, Nullable]
- EXPECT_THAT(nullVec("NullableTuple<int*, int* _Nonnull>::type"),
- ElementsAre());
+ EXPECT_THAT(
+ nullVec("NullableTuple<int*, int* _Nonnull>::type"),
+ ElementsAre(NullabilityKind::Unspecified, NullabilityKind::Unspecified));
}
class PrintWithNullabilityTest : public ::testing::Test {