Skip to content

[Clang][Sema] Use StructuralValues to model dependent NTTP arguments #93556

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 3 commits into from
May 29, 2024

Conversation

zyn0217
Copy link
Contributor

@zyn0217 zyn0217 commented May 28, 2024

This patch takes Richard's approach of no longer modeling dependent NTTP arguments with TemplateParamObjectDecls. Clang used to do so, which left behind a problem in that we might mess up dependent and non-dependent arguments that boil down to the same canonical type because there's a default argument on the NTTP.

The problem of "canonical expression" is still present because this patch doesn't touch the profiling part. Namely, #92292 seems different.

Fixes #84052

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels May 28, 2024
@llvmbot
Copy link
Member

llvmbot commented May 28, 2024

@llvm/pr-subscribers-clang

Author: Younan Zhang (zyn0217)

Changes

This patch takes Richard's approach of no longer modeling dependent NTTP arguments with TemplateParamObjectDecls. Clang used to do so, which left behind a problem in that we might mess up dependent and non-dependent arguments that boil down to the same canonical type because there's a default argument on the NTTP.

The problem of "canonical expression" is still present because this patch doesn't touch the profiling part. Namely, #92292 seems different.

Fixes #84052


Full diff: https://github.com/llvm/llvm-project/pull/93556.diff

3 Files Affected:

  • (modified) clang/docs/ReleaseNotes.rst (+1)
  • (modified) clang/lib/AST/TemplateBase.cpp (+6-1)
  • (modified) clang/test/SemaTemplate/temp_arg_nontype_cxx2c.cpp (+22-1)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 49ab222bec405..722efd08721c3 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -710,6 +710,7 @@ Bug Fixes to C++ Support
 - Correctly treat the compound statement of an ``if consteval`` as an immediate context. Fixes (#GH91509).
 - When partial ordering alias templates against template template parameters,
   allow pack expansions when the alias has a fixed-size parameter list. Fixes (#GH62529).
+- Clang no longer models dependent NTTP arguments as ``TemplateParamObjectDecl``s. Fixes (#GH84052).
 
 Bug Fixes to AST Handling
 ^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/lib/AST/TemplateBase.cpp b/clang/lib/AST/TemplateBase.cpp
index 3310d7dc24c59..e6b23eddb5888 100644
--- a/clang/lib/AST/TemplateBase.cpp
+++ b/clang/lib/AST/TemplateBase.cpp
@@ -221,8 +221,13 @@ static const ValueDecl *getAsSimpleValueDeclRef(const ASTContext &Ctx,
 
   // We model class non-type template parameters as their template parameter
   // object declaration.
-  if (V.isStruct() || V.isUnion())
+  if (V.isStruct() || V.isUnion()) {
+    // Dependent types are not supposed to be described as
+    // TemplateParamObjectDecls.
+    if (T->isDependentType() || T->isInstantiationDependentType())
+      return nullptr;
     return Ctx.getTemplateParamObjectDecl(T, V);
+  }
 
   // Pointers and references with an empty path use the special 'Declaration'
   // representation.
diff --git a/clang/test/SemaTemplate/temp_arg_nontype_cxx2c.cpp b/clang/test/SemaTemplate/temp_arg_nontype_cxx2c.cpp
index 9fb6b440b6b2a..e74c031eba4c1 100644
--- a/clang/test/SemaTemplate/temp_arg_nontype_cxx2c.cpp
+++ b/clang/test/SemaTemplate/temp_arg_nontype_cxx2c.cpp
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -fsyntax-only -std=c++20 -Wconversion -verify %s
+// RUN: %clang_cc1 -fsyntax-only -std=c++2c -Wconversion -verify %s
 
 struct Test {
     int a = 0;
@@ -102,3 +102,24 @@ void bar() {
 }
 
 }
+
+namespace GH84052 {
+
+template <class... T>
+concept C = sizeof(T...[1]) == 1; // #C
+
+struct A {};
+
+template <class T, C<T> auto = A{}> struct Set {}; // #Set
+
+template <class T> void foo() {
+  Set<T> unrelated;
+}
+
+Set<bool> sb;
+Set<float> sf;
+// expected-error@-1 {{constraints not satisfied for class template 'Set'}}
+// expected-note@#Set {{because 'C<decltype(GH84052::A{}), float>' evaluated to false}}
+// expected-note@#C {{evaluated to false}}
+
+} // namespace GH84052

Copy link
Collaborator

@erichkeane erichkeane left a comment

Choose a reason for hiding this comment

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

This change seems smaller than I expected here, so I guess I'm not entirely sure what is going on. Can you better explain how this ends up using structural values instead of the TemplateParamObjectDecl?

// Dependent types are not supposed to be described as
// TemplateParamObjectDecls.
if (T->isDependentType() || T->isInstantiationDependentType())
return nullptr;
Copy link
Collaborator

Choose a reason for hiding this comment

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

What do we end up modeling them as instead?

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 only caller of getAsSimpleValueDeclRef is TemplateArgument's constructor (for an NTTP establishment), where we basically try the following things in order, IIUC:

  1. integral type
  2. nullptrs
  3. declarations (this is where we called getAsSimpleValueDeclRef and built up a TemplateParamObjectDecl if the function does return a value)

otherwise, a structural type.

TemplateArgument::TemplateArgument(const ASTContext &Ctx, QualType Type,
const APValue &V, bool IsDefaulted) {
if (Type->isIntegralOrEnumerationType() && V.isInt())
initFromIntegral(Ctx, V.getInt(), Type, IsDefaulted);
else if ((V.isLValue() && V.isNullPointer()) ||
(V.isMemberPointer() && !V.getMemberPointerDecl()))
initFromType(Type, /*isNullPtr=*/true, IsDefaulted);
else if (const ValueDecl *VD = getAsSimpleValueDeclRef(Ctx, Type, V))
// FIXME: The Declaration form should expose a const ValueDecl*.
initFromDeclaration(const_cast<ValueDecl *>(VD), Type, IsDefaulted);
else
initFromStructural(Ctx, Type, V, IsDefaulted);
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah! Thanks! That explains it.

@zyn0217
Copy link
Contributor Author

zyn0217 commented May 28, 2024

Thanks for the review. I'm also waiting for @mizvekov in case he has some opinions.

@mizvekov
Copy link
Contributor

LGTM as well, thanks!

@zyn0217 zyn0217 merged commit bb42511 into llvm:main May 29, 2024
8 checks passed
@zyn0217 zyn0217 deleted the GH84052 branch May 29, 2024 04:58
vg0204 pushed a commit to vg0204/llvm-project that referenced this pull request May 29, 2024
…lvm#93556)

This patch takes Richard's approach of no longer modeling dependent NTTP
arguments with TemplateParamObjectDecls. Clang used to do so, which left
behind a problem in that we might mess up dependent and non-dependent
arguments that boil down to the same canonical type because there's a
default argument on the NTTP.

The problem of "canonical expression" is still present because this
patch doesn't touch the profiling part. Namely, llvm#92292 seems different.

Fixes llvm#84052
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Evaluating constraints on an NTTP with a default template argument leads to a crash
4 participants