Skip to content

[clang][Sema] Fix initialization of NonTypeTemplateParmDecl... #121768

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Feb 19, 2025
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions clang/include/clang/AST/DeclTemplate.h
Original file line number Diff line number Diff line change
Expand Up @@ -1365,6 +1365,8 @@ class NonTypeTemplateParmDecl final
DefaultArgStorage<NonTypeTemplateParmDecl, TemplateArgumentLoc *>;
DefArgStorage DefaultArgument;

bool PlaceholderTypeConstraintInitialized = false;

// FIXME: Collapse this into TemplateParamPosition; or, just move depth/index
// down here to save memory.

Expand Down Expand Up @@ -1533,13 +1535,11 @@ class NonTypeTemplateParmDecl final
/// Return the constraint introduced by the placeholder type of this non-type
/// template parameter (if any).
Expr *getPlaceholderTypeConstraint() const {
return hasPlaceholderTypeConstraint() ? *getTrailingObjects<Expr *>() :
nullptr;
return PlaceholderTypeConstraintInitialized ? *getTrailingObjects<Expr *>()
: nullptr;
}

void setPlaceholderTypeConstraint(Expr *E) {
*getTrailingObjects<Expr *>() = E;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I like the assert-safety that we get from the PlaceholderTypeConstraintInitialized, I'm not sure it is worth the rigamarole. Why not just have the ctor do setPlaceholderTypeConstraint?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both inside Sema and ASTReaderDecl the NonTypeTemplateParmDecl is created relatively far from the parsing / deserialization of the constraint.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not complaining about the 'set' method, just that the concern is that it is uninitialized. I'm saying, why not just initialize it to nullptr? Or am I misunderstanding the concern.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The flag would allow to discern between "was not initialized" and "was explicitly initialized to null".
Now, I have to admit I do not know if the latter is meaningful for a constrained NTTP.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, then we're on the same page. I do not see a difference between "explicitly null" and "not initialized" here. I think that is a differentiation without a cause.

}
void setPlaceholderTypeConstraint(Expr *E);

/// Determine whether this non-type template parameter's type has a
/// placeholder with a type-constraint.
Expand Down
10 changes: 10 additions & 0 deletions clang/lib/AST/DeclTemplate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -853,6 +853,16 @@ void NonTypeTemplateParmDecl::setDefaultArgument(
DefaultArgument.set(new (C) TemplateArgumentLoc(DefArg));
}

void NonTypeTemplateParmDecl::setPlaceholderTypeConstraint(Expr *E) {
assert(hasPlaceholderTypeConstraint() &&
"setPlaceholderTypeConstraint called on a NonTypeTemplateParmDecl "
"without constraint");
assert(!PlaceholderTypeConstraintInitialized && "PlaceholderTypeConstraint "
"was already initialized!");
*getTrailingObjects<Expr *>() = E;
PlaceholderTypeConstraintInitialized = true;
}

//===----------------------------------------------------------------------===//
// TemplateTemplateParmDecl Method Implementations
//===----------------------------------------------------------------------===//
Expand Down
6 changes: 4 additions & 2 deletions clang/lib/Serialization/ASTReaderDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2703,8 +2703,10 @@ void ASTDeclReader::VisitNonTypeTemplateParmDecl(NonTypeTemplateParmDecl *D) {
// TemplateParmPosition.
D->setDepth(Record.readInt());
D->setPosition(Record.readInt());
if (D->hasPlaceholderTypeConstraint())
D->setPlaceholderTypeConstraint(Record.readExpr());
if (D->hasPlaceholderTypeConstraint()) {
if (Record.readBool()) // PlaceholderTypeConstraintInitialized
D->setPlaceholderTypeConstraint(Record.readExpr());
}
if (D->isExpandedParameterPack()) {
auto TypesAndInfos =
D->getTrailingObjects<std::pair<QualType, TypeSourceInfo *>>();
Expand Down
13 changes: 9 additions & 4 deletions clang/lib/Serialization/ASTWriterDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1985,17 +1985,22 @@ void ASTDeclWriter::VisitNonTypeTemplateParmDecl(NonTypeTemplateParmDecl *D) {
// For an expanded parameter pack, record the number of expansion types here
// so that it's easier for deserialization to allocate the right amount of
// memory.
Expr *TypeConstraint = D->getPlaceholderTypeConstraint();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes here are all NFC, right? Or am I missing a purpose to this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a difference: D->hasPlaceholderTypeConstraint() may be true, but D->getPlaceholderTypeConstraint may return nullptr.
IIUC, before this patch there was a underlying assumption that the former is true if and only if the latter is not nullptr.

This is not aligned when there are parsing errors.

See my comment here.

Record.push_back(!!TypeConstraint);
Record.push_back(D->hasPlaceholderTypeConstraint());
if (D->isExpandedParameterPack())
Record.push_back(D->getNumExpansionTypes());

VisitDeclaratorDecl(D);
// TemplateParmPosition.
Record.push_back(D->getDepth());
Record.push_back(D->getPosition());
if (TypeConstraint)
Record.AddStmt(TypeConstraint);

if (D->hasPlaceholderTypeConstraint()) {
Expr *TypeConstraint = D->getPlaceholderTypeConstraint();
Record.push_back(/*PlaceholderTypeConstraintInitialized=*/TypeConstraint !=
nullptr);
if (TypeConstraint)
Record.AddStmt(TypeConstraint);
}

if (D->isExpandedParameterPack()) {
for (unsigned I = 0, N = D->getNumExpansionTypes(); I != N; ++I) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// RUN: rm -rf %t
// RUN: split-file %s %t
// RUN: cd %t

// RUN: %clang_cc1 -std=c++20 mod.cppm -emit-module-interface -o mod.pcm -fallow-pcm-with-compiler-errors -verify
// RUN: %clang_cc1 -std=c++20 main.cpp -fmodule-file=mod=mod.pcm -verify -fallow-pcm-with-compiler-errors -fsyntax-only -ast-dump-all | FileCheck %s
Comment on lines +5 to +6
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if this also manifests with just pch serialization - it would always be great if we can simplify the test

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't even need serialization. IIRC it can be triggered with an ast dump, but only if the uninitialized value happens not to be a 0, which is the tricky part to trigger (not using sanitizers, that is).


// RUN: %clang_cc1 -std=c++20 mod.cppm -emit-reduced-module-interface -o mod.pcm -fallow-pcm-with-compiler-errors -verify
// RUN: %clang_cc1 -std=c++20 main.cpp -fmodule-file=mod=mod.pcm -verify -fallow-pcm-with-compiler-errors -fsyntax-only -ast-dump-all | FileCheck %s

//--- mod.cppm
export module mod;

template <typename T, auto Q>
concept ReferenceOf = Q;

// expected-error@+2 {{unknown type name 'AngleIsInvalidNow'}}
// expected-error@+1 {{constexpr variable 'angle' must be initialized by a constant expression}}
constexpr struct angle {AngleIsInvalidNow e;} angle;

// expected-error@+1 {{non-type template argument is not a constant expression}}
template<ReferenceOf<angle> auto R, typename Rep> requires requires(Rep v) {cos(v);}
auto cos(const Rep& q);

// expected-error@+1 {{non-type template argument is not a constant expression}}
template<ReferenceOf<angle> auto R, typename Rep> requires requires(Rep v) {tan(v);}
auto tan(const Rep& q);

//--- main.cpp
// expected-no-diagnostics
import mod;

// CHECK: |-FunctionTemplateDecl {{.*}} <line:11:1, line:12:22> col:6 imported in mod hidden invalid cos
// CHECK-NEXT: | |-NonTypeTemplateParmDecl {{.*}} <line:11:10, col:34> col:34 imported in mod hidden referenced invalid 'ReferenceOf<angle> auto' depth 0 index 0 R
// CHECK-NEXT: | |-TemplateTypeParmDecl {{.*}} <col:37, col:46> col:46 imported in mod hidden referenced typename depth 0 index 1 Rep
// CHECK-NEXT: | |-RequiresExpr {{.*}} <col:60, col:84> 'bool'
// CHECK-NEXT: | | |-ParmVarDecl {{.*}} <col:69, col:73> col:73 imported in mod hidden referenced v 'Rep'
// CHECK-NEXT: | | `-SimpleRequirement {{.*}} dependent
// CHECK-NEXT: | | `-CallExpr {{.*}} <col:77, col:82> '<dependent type>'
// CHECK-NEXT: | | |-UnresolvedLookupExpr {{.*}} <col:77> '<overloaded function type>' lvalue (ADL) = 'cos' empty
// CHECK-NEXT: | | `-DeclRefExpr {{.*}} <col:81> 'Rep' lvalue ParmVar {{.*}} 'v' 'Rep' non_odr_use_unevaluated
// CHECK-NEXT: | `-FunctionDecl {{.*}} <line:12:1, col:22> col:6 imported in mod hidden cos 'auto (const Rep &)'
// CHECK-NEXT: | `-ParmVarDecl {{.*}} <col:10, col:21> col:21 imported in mod hidden q 'const Rep &'

// CHECK: |-FunctionTemplateDecl {{.*}} <line:15:1, line:16:22> col:6 imported in mod hidden invalid tan
// CHECK-NEXT: | |-NonTypeTemplateParmDecl {{.*}} <line:15:10, col:34> col:34 imported in mod hidden referenced invalid 'ReferenceOf<angle> auto' depth 0 index 0 R
// CHECK-NEXT: | |-TemplateTypeParmDecl {{.*}} <col:37, col:46> col:46 imported in mod hidden referenced typename depth 0 index 1 Rep
// CHECK-NEXT: | |-RequiresExpr {{.*}} <col:60, col:84> 'bool'
// CHECK-NEXT: | | |-ParmVarDecl {{.*}} <col:69, col:73> col:73 imported in mod hidden referenced v 'Rep'
// CHECK-NEXT: | | `-SimpleRequirement {{.*}} dependent
// CHECK-NEXT: | | `-CallExpr {{.*}} <col:77, col:82> '<dependent type>'
// CHECK-NEXT: | | |-UnresolvedLookupExpr {{.*}} <col:77> '<overloaded function type>' lvalue (ADL) = 'tan' empty
// CHECK-NEXT: | | `-DeclRefExpr {{.*}} <col:81> 'Rep' lvalue ParmVar {{.*}} 'v' 'Rep' non_odr_use_unevaluated
// CHECK-NEXT: | `-FunctionDecl {{.*}} <line:16:1, col:22> col:6 imported in mod hidden tan 'auto (const Rep &)'
// CHECK-NEXT: | `-ParmVarDecl {{.*}} <col:10, col:21> col:21 imported in mod hidden q 'const Rep &'
Comment on lines +33 to +55
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please filter out line numbers on the AST dump match, otherwise this makes these tests hard to update.

Loading