Skip to content

[Clang][Sema] fix a bug on constraint check with template friend function #90646

New issue

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

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

Already on GitHub? # to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

jcsxky
Copy link
Contributor

@jcsxky jcsxky commented Apr 30, 2024

attempt to fix #90349
Skip to add outer class template arguments to MTAL when the friend function has the same depth with its lexical context(CXXRecordDecl).

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

llvmbot commented Apr 30, 2024

@llvm/pr-subscribers-clang

Author: Qizhi Hu (jcsxky)

Changes

attempt to fix #90349
Skip to add outer class template arguments to MTAL when the friend function has the same depth with its lexical context(CXXRecordDecl).


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

3 Files Affected:

  • (modified) clang/docs/ReleaseNotes.rst (+1)
  • (modified) clang/lib/Sema/SemaTemplateInstantiate.cpp (+14)
  • (added) clang/test/SemaCXX/PR90349.cpp (+43)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index c11f117cd6e8b4..ec10c82a3a5403 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -627,6 +627,7 @@ Bug Fixes to C++ Support
 - Fix a bug on template partial specialization with issue on deduction of nontype template parameter
   whose type is `decltype(auto)`. Fixes (#GH68885).
 - Clang now correctly treats the noexcept-specifier of a friend function to be a complete-class context.
+- Fix a bug on constraint check with template friend function. Fixes (#GH90349).
 
 Bug Fixes to AST Handling
 ^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp
index 3a9fd906b7af86..1805f8f6e5ad90 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -281,6 +281,20 @@ Response HandleFunction(Sema &SemaRef, const FunctionDecl *Function,
     if (Function->getPrimaryTemplate()->isMemberSpecialization())
       return Response::Done();
 
+    if (Function->getFriendObjectKind())
+      if (const ClassTemplateSpecializationDecl *TD =
+              dyn_cast<ClassTemplateSpecializationDecl>(
+                  Function->getLexicalDeclContext())) {
+        const CXXRecordDecl *TemplatePattern =
+            TD->getTemplateInstantiationPattern();
+        const FunctionDecl *FunctionPattern =
+            Function->getTemplateInstantiationPattern();
+        if (TemplatePattern && FunctionPattern &&
+            TemplatePattern->getTemplateDepth() ==
+                FunctionPattern->getTemplateDepth())
+          return Response::Done();
+      }
+
     // If this function is a generic lambda specialization, we are done.
     if (!ForConstraintInstantiation &&
         isGenericLambdaCallOperatorOrStaticInvokerSpecialization(Function)) {
diff --git a/clang/test/SemaCXX/PR90349.cpp b/clang/test/SemaCXX/PR90349.cpp
new file mode 100644
index 00000000000000..6a4b5c21e88f3b
--- /dev/null
+++ b/clang/test/SemaCXX/PR90349.cpp
@@ -0,0 +1,43 @@
+// RUN: %clang_cc1 -verify -std=c++20 -fsyntax-only %s
+
+// expected-no-diagnostics
+
+namespace std {
+template<class T>
+concept floating_point = __is_same(T,double) || __is_same(T,float);
+
+template<class T>
+concept integral = __is_same(T,int);
+
+}
+
+template<std::integral T, std::floating_point Float>
+class Blob;
+
+template<std::floating_point Float, std::integral T>
+Blob<T, Float> MakeBlob();
+
+template<std::integral T, std::floating_point Float>
+class Blob {
+private:
+    Blob() {}
+
+    friend Blob<T, Float> MakeBlob<Float, T>();
+};
+
+template<std::floating_point Float, std::integral T>
+Blob<T, Float> MakeBlob()
+{
+    return Blob<T, Float>();
+}
+
+template<std::floating_point Float, std::integral T>
+Blob<T, Float> FindBlobs()
+{
+    return MakeBlob<Float, T>();
+}
+
+int main(int argc, const char * argv[]) {
+    FindBlobs<double, int>();
+    return 0;
+}

@sdkrystian
Copy link
Member

sdkrystian commented Apr 30, 2024

I'm actually working on constraint checking for function template specializations in #88963. I don't think this patch is quite right... this will cause a crash if the befriended function is a member of a class template specialization. Relative to the changes in #88963, I believe the correct fix would be to change line 278 to:

if (RelativeToPrimary &&
    (Function->getTemplateSpecializationKind() ==
         TSK_ExplicitSpecialization ||
     (Function->getFriendObjectKind() &&
      !Function->getPrimaryTemplate()->getFriendObjectKind())))
  return Response::UseNextDecl(Function);

I added a commit to #88963 which makes this change (be79079)

cc @erichkeane

@jcsxky
Copy link
Contributor Author

jcsxky commented Apr 30, 2024

I'm actually working on constraint checking for function template specializations in #88963. I don't think this patch is quite right... this will cause a crash if the befriended function is a member of a class template specialization. Relative to the changes in #88963, I believe the correct fix would be to change line 278 to:

if (RelativeToPrimary &&
    (Function->getTemplateSpecializationKind() ==
         TSK_ExplicitSpecialization ||
     (Function->getFriendObjectKind() &&
      !Function->getPrimaryTemplate()->getFriendObjectKind())))
  return Response::UseNextDecl(Function);

I added a commit to #88963 which makes this change (be79079)

cc @erichkeane

Only when the friend function doesn't use any other new template parameters, i.e. their depth is equal can we skip to add the outer arguments to MTAL.

this will cause a crash if the befriended function is a member of a class template specialization

Could you please provide a testcase?

@jcsxky
Copy link
Contributor Author

jcsxky commented May 1, 2024

Windows CI failed with some unrelated files.

@erichkeane erichkeane requested a review from mizvekov May 1, 2024 13:04
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 seems reasonable, @mizvekov is doing more work in this area, so I'd like him to make sure this isn't something he's fixed elsewhere.

@sdkrystian
Copy link
Member

sdkrystian commented May 1, 2024

template<typename T, typename U>
concept D = sizeof(T) == sizeof(U);

template<typename T>
struct A
{
    template<typename U, typename V> requires D<U, V>
    static void f();
};

template<typename T, typename U>
struct B
{
    template<typename V>
    struct C
    {
        friend void A<char>::f<T, U>();
    };
};

template struct B<int, int>::C<short>;

extern template void A<char>::f<int, int>(); // crash here

@jcsxky causes crash with and without this patch applied.

@jcsxky
Copy link
Contributor Author

jcsxky commented May 1, 2024

template<typename T, typename U>
concept D = sizeof(T) == sizeof(U);

template<typename T>
struct A
{
    template<typename U, typename V> requires D<U, V>
    static void f();
};

template<typename T, typename U>
struct B
{
    template<typename V>
    struct C
    {
        friend void A<char>::f<T, U>();
    };
};

template struct B<int, int>::C<short>;

extern template void A<char>::f<int, int>(); // crash here

@jcsxky causes crash with and without this patch applied.

Thanks for your feedback! This may be another issue. clang trunk crashes with this case.

@mizvekov
Copy link
Contributor

mizvekov commented May 1, 2024

I agree with @sdkrystian, even though the test crashes for maybe yet another reason, it demonstrates you can friend a function from a different template context, so comparing the depths from different branches is not helpful.

@sdkrystian
Copy link
Member

FWIW, the changes in be79079 fix the crash

@jcsxky
Copy link
Contributor Author

jcsxky commented May 2, 2024

I agree with @sdkrystian, even though the test crashes for maybe yet another reason, it demonstrates you can friend a function from a different template context, so comparing the depths from different branches is not helpful.

@mizvekov I missed the case which friend function is declared in an inner class, and 3d27f11 demonstrates the comparison works fine.

@sdkrystian
Copy link
Member

This still doesn't handle cases such as:

template<typename T, typename U, typename V, typename W>
concept E = sizeof(T) == sizeof(U) && sizeof(V) == sizeof(W);

template<typename T> // T = char
struct A
{
    template<typename U> // U = signed char
    struct B
    {
        template<typename V, typename W, typename X> // V = int, W = int, X = short
        requires E<T, U, V, W> // incorrectly substitutes T = int, U = short, V = int, V = int
        static void f();
    };
};


template<typename T, typename U> // T = int, U = int
struct C
{
    template<typename V> // V = short
    struct D
    {
        friend void A<char>::B<signed char>::f<T, U, V>();
    };
};

template struct C<int, int>::D<short>;

extern template void A<char>::B<signed char>::f<int, int, short>();

(which is handled by be79079)

@jcsxky
Copy link
Contributor Author

jcsxky commented May 2, 2024

This still doesn't handle cases such as:

template<typename T, typename U, typename V, typename W>
concept E = sizeof(T) == sizeof(U) && sizeof(V) == sizeof(W);

template<typename T> // T = char
struct A
{
    template<typename U> // U = signed char
    struct B
    {
        template<typename V, typename W, typename X> // V = int, W = int, X = short
        requires E<T, U, V, W> // incorrectly substitutes T = int, U = short, V = int, V = int
        static void f();
    };
};


template<typename T, typename U> // T = int, U = int
struct C
{
    template<typename V> // V = short
    struct D
    {
        friend void A<char>::B<signed char>::f<T, U, V>();
    };
};

template struct C<int, int>::D<short>;

extern template void A<char>::B<signed char>::f<int, int, short>();

(which is handled by be79079)

Thanks for your patience and testcases! They really make me understand these related issues more clearly.

# for free to join this conversation on GitHub. Already have an account? # 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.

Incorrect constraint check with template friend function
5 participants