Skip to content

[clang] Reland: Instantiate alias templates with sugar #101858

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

Conversation

mizvekov
Copy link
Contributor

@mizvekov mizvekov commented Aug 4, 2024

This makes use of the changes introduced in D134604, in order to instantiate alias templates witn a final sugared substitution.

This comes at no additional relevant cost.
Since we don't track / unique them in specializations, we wouldn't be able to resugar them later anyway.

Differential Revision: https://reviews.llvm.org/D136565

@mizvekov mizvekov self-assigned this Aug 4, 2024
@mizvekov mizvekov requested a review from JDevlieghere as a code owner August 4, 2024 01:23
@llvmbot llvmbot added clang Clang issues not falling into any other category clang-tools-extra lldb clangd clang-tidy clang:frontend Language frontend issues, e.g. anything involving "Sema" HLSL HLSL Language Support labels Aug 4, 2024
@llvmbot
Copy link
Member

llvmbot commented Aug 4, 2024

@llvm/pr-subscribers-clangd
@llvm/pr-subscribers-clang-tidy
@llvm/pr-subscribers-clang-tools-extra
@llvm/pr-subscribers-hlsl

@llvm/pr-subscribers-clang

Author: Matheus Izvekov (mizvekov)

Changes

This makes use of the changes introduced in D134604, in order to instantiate alias templates witn a final sugared substitution.

This comes at no additional relevant cost.
Since we don't track / unique them in specializations, we wouldn't be able to resugar them later anyway.

Differential Revision: https://reviews.llvm.org/D136565


Patch is 31.43 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/101858.diff

15 Files Affected:

  • (modified) clang-tools-extra/clangd/unittests/HoverTests.cpp (+1-1)
  • (modified) clang-tools-extra/test/clang-tidy/checkers/bugprone/unused-local-non-trivial-variable.cpp (+1-1)
  • (modified) clang/lib/Sema/SemaTemplate.cpp (+2-2)
  • (modified) clang/test/AST/ast-dump-template-decls.cpp (+8-27)
  • (modified) clang/test/CXX/temp/temp.deduct.guide/p3.cpp (+1-1)
  • (modified) clang/test/Misc/diag-template-diffing-cxx11.cpp (+9-12)
  • (modified) clang/test/SemaCXX/nullability.cpp (+2-2)
  • (modified) clang/test/SemaCXX/sizeless-1.cpp (+1-1)
  • (modified) clang/test/SemaHLSL/VectorOverloadResolution.hlsl (+5-5)
  • (modified) clang/test/SemaTemplate/deduction-guide.cpp (+35-35)
  • (modified) clang/test/SemaTemplate/make_integer_seq.cpp (+2-6)
  • (modified) clang/test/SemaTemplate/temp_arg_nontype.cpp (+1-1)
  • (modified) lldb/test/API/commands/expression/import-std-module/shared_ptr/TestSharedPtrFromStdModule.py (+3-3)
  • (modified) lldb/test/API/commands/expression/import-std-module/weak_ptr-dbg-info-content/TestDbgInfoContentWeakPtrFromStdModule.py (+1-1)
  • (modified) lldb/test/API/commands/expression/import-std-module/weak_ptr/TestWeakPtrFromStdModule.py (+3-3)
diff --git a/clang-tools-extra/clangd/unittests/HoverTests.cpp b/clang-tools-extra/clangd/unittests/HoverTests.cpp
index 8d6d4223d7260..69f6df46c87ce 100644
--- a/clang-tools-extra/clangd/unittests/HoverTests.cpp
+++ b/clang-tools-extra/clangd/unittests/HoverTests.cpp
@@ -1325,7 +1325,7 @@ class Foo final {})cpp";
          HI.LocalScope = "";
          HI.Kind = index::SymbolKind::TypeAlias;
          HI.Definition = "template <typename T> using AA = A<T>";
-         HI.Type = {"A<T>", "type-parameter-0-0"}; // FIXME: should be 'T'
+         HI.Type = {"A<T>", "T"};
          HI.TemplateParameters = {
              {{"typename"}, std::string("T"), std::nullopt}};
        }},
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unused-local-non-trivial-variable.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unused-local-non-trivial-variable.cpp
index 3fdc24b94a6cb..721c55b1fb538 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unused-local-non-trivial-variable.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unused-local-non-trivial-variable.cpp
@@ -76,7 +76,7 @@ T qux(T Generic) {
     async::Future<T> TemplateType;
     // CHECK-MESSAGES: :[[@LINE-1]]:22: warning: unused local variable 'TemplateType' of type 'async::Future<T>' [bugprone-unused-local-non-trivial-variable]
     a::Future<T> AliasTemplateType;
-    // CHECK-MESSAGES: :[[@LINE-1]]:18: warning: unused local variable 'AliasTemplateType' of type 'a::Future<T>' (aka 'Future<type-parameter-0-0>') [bugprone-unused-local-non-trivial-variable]
+    // CHECK-MESSAGES: :[[@LINE-1]]:18: warning: unused local variable 'AliasTemplateType' of type 'a::Future<T>' (aka 'Future<T>') [bugprone-unused-local-non-trivial-variable]
     [[maybe_unused]] async::Future<Units> MaybeUnused;
     return Generic;
 }
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index 720862ec0bffa..1346a4a3f0012 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -3334,8 +3334,8 @@ QualType Sema::CheckTemplateIdType(TemplateName Name,
 
     // Only substitute for the innermost template argument list.
     MultiLevelTemplateArgumentList TemplateArgLists;
-    TemplateArgLists.addOuterTemplateArguments(Template, CanonicalConverted,
-                                               /*Final=*/false);
+    TemplateArgLists.addOuterTemplateArguments(Template, SugaredConverted,
+                                               /*Final=*/true);
     TemplateArgLists.addOuterRetainedLevels(
         AliasTemplate->getTemplateParameters()->getDepth());
 
diff --git a/clang/test/AST/ast-dump-template-decls.cpp b/clang/test/AST/ast-dump-template-decls.cpp
index f0a6204ce3cfa..9f578e5afe561 100644
--- a/clang/test/AST/ast-dump-template-decls.cpp
+++ b/clang/test/AST/ast-dump-template-decls.cpp
@@ -123,8 +123,6 @@ using type2 = typename C<int>::type1<void>;
 // CHECK-NEXT: TemplateArgument type 'void'
 // CHECK-NEXT: BuiltinType 0x{{[^ ]*}} 'void'
 // CHECK-NEXT: FunctionProtoType 0x{{[^ ]*}} 'void (int)' cdecl
-// CHECK-NEXT: SubstTemplateTypeParmType 0x{{[^ ]*}} 'void' sugar class depth 0 index 0 U
-// CHECK-NEXT: TypeAliasTemplate 0x{{[^ ]*}} 'type1'
 // CHECK-NEXT: BuiltinType 0x{{[^ ]*}} 'void'
 // CHECK-NEXT: SubstTemplateTypeParmType 0x{{[^ ]*}} 'int' sugar class depth 0 index 0 T
 // CHECK-NEXT: ClassTemplateSpecialization 0x{{[^ ]*}} 'C'
@@ -132,39 +130,24 @@ using type2 = typename C<int>::type1<void>;
 } // namespace PR55886
 
 namespace PR56099 {
-template <typename... As> struct Y;
-template <typename... Bs> using Z = Y<Bs...>;
-template <typename... Cs> struct foo {
-  template <typename... Ds> using bind = Z<Ds..., Cs...>;
-};
-using t1 = foo<int, short>::bind<char, float>;
-// CHECK:      TemplateSpecializationType 0x{{[^ ]*}} 'Y<char, float, int, short>' sugar
-// CHECK:      SubstTemplateTypeParmType 0x{{[^ ]*}} 'char' sugar typename depth 0 index 0 ... Bs pack_index 3
-// CHECK-NEXT: TypeAliasTemplate 0x{{[^ ]*}} 'Z'
-// CHECK:      SubstTemplateTypeParmType 0x{{[^ ]*}} 'float' sugar typename depth 0 index 0 ... Bs pack_index 2
-// CHECK-NEXT: TypeAliasTemplate 0x{{[^ ]*}} 'Z'
-// CHECK:      SubstTemplateTypeParmType 0x{{[^ ]*}} 'int' sugar typename depth 0 index 0 ... Bs pack_index 1
-// CHECK-NEXT: TypeAliasTemplate 0x{{[^ ]*}} 'Z'
-// CHECK:      SubstTemplateTypeParmType 0x{{[^ ]*}} 'short' sugar typename depth 0 index 0 ... Bs pack_index 0
-// CHECK-NEXT: TypeAliasTemplate 0x{{[^ ]*}} 'Z'
-
 template <typename... T> struct D {
-  template <typename... U> using B = int(int (*...p)(T, U));
+  template <typename... U> struct bind {
+    using bound_type = int(int (*...p)(T, U));
+  };
 };
-using t2 = D<float, char>::B<int, short>;
-// CHECK:      TemplateSpecializationType 0x{{[^ ]*}} 'B<int, short>' sugar alias{{$}}
-// CHECK-NEXT: name: 'D<float, char>::B':'PR56099::D<float, char>::B' qualified
+template struct D<float, char>::bind<int, short>;
+// CHECK:      TypeAliasDecl 0x{{[^ ]*}} <line:{{[1-9]+}}:5, col:45> col:11 bound_type 'int (int (*)(float, int), int (*)(char, short))'
 // CHECK:      FunctionProtoType 0x{{[^ ]*}} 'int (int (*)(float, int), int (*)(char, short))' cdecl
 // CHECK:      FunctionProtoType 0x{{[^ ]*}} 'int (float, int)' cdecl
 // CHECK:      SubstTemplateTypeParmType 0x{{[^ ]*}} 'float' sugar typename depth 0 index 0 ... T pack_index 1
 // CHECK-NEXT: ClassTemplateSpecialization 0x{{[^ ]*}} 'D'
 // CHECK:      SubstTemplateTypeParmType 0x{{[^ ]*}} 'int' sugar typename depth 0 index 0 ... U pack_index 1
-// CHECK-NEXT: TypeAliasTemplate 0x{{[^ ]*}} 'B'
+// CHECK-NEXT: ClassTemplateSpecialization 0x{{[^ ]*}} 'bind'
 // CHECK:      FunctionProtoType 0x{{[^ ]*}} 'int (char, short)' cdecl
 // CHECK:      SubstTemplateTypeParmType 0x{{[^ ]*}} 'char' sugar typename depth 0 index 0 ... T pack_index 0
 // CHECK-NEXT: ClassTemplateSpecialization 0x{{[^ ]*}} 'D'
 // CHECK:      SubstTemplateTypeParmType 0x{{[^ ]*}} 'short' sugar typename depth 0 index 0 ... U pack_index 0
-// CHECK-NEXT: TypeAliasTemplate 0x{{[^ ]*}} 'B'
+// CHECK-NEXT: ClassTemplateSpecialization 0x{{[^ ]*}} 'bind'
 } // namespace PR56099
 
 namespace subst_default_argument {
@@ -178,9 +161,7 @@ using test1 = D<E, int>;
 // CHECK-NEXT: |-name: 'A':'subst_default_argument::A' qualified
 // CHECK-NEXT: | `-ClassTemplateDecl {{.+}} A
 // CHECK-NEXT: |-TemplateArgument type 'int'
-// CHECK-NEXT: | `-SubstTemplateTypeParmType 0x{{[^ ]*}} 'int' sugar class depth 0 index 1 D2
-// CHECK-NEXT: |   |-TypeAliasTemplate 0x{{[^ ]*}} 'D'
-// CHECK-NEXT: |   `-BuiltinType 0x{{[^ ]*}} 'int'
+// CHECK-NEXT: | `-BuiltinType 0x{{[^ ]*}} 'int'
 // CHECK-NEXT: `-RecordType 0x{{[^ ]*}} 'subst_default_argument::A<int>'
 // CHECK-NEXT:   `-ClassTemplateSpecialization 0x{{[^ ]*}} 'A'
 } // namespace subst_default_argument
diff --git a/clang/test/CXX/temp/temp.deduct.guide/p3.cpp b/clang/test/CXX/temp/temp.deduct.guide/p3.cpp
index cdc073066f3e3..c5404847beb06 100644
--- a/clang/test/CXX/temp/temp.deduct.guide/p3.cpp
+++ b/clang/test/CXX/temp/temp.deduct.guide/p3.cpp
@@ -33,7 +33,7 @@ template<template<typename> typename TT> struct E { // expected-note 2{{template
 };
 
 A(int) -> int; // expected-error {{deduced type 'int' of deduction guide is not a specialization of template 'A'}}
-template<typename T> A(T) -> B<T>; // expected-error {{deduced type 'B<T>' (aka 'A<type-parameter-0-0>') of deduction guide is not written as a specialization of template 'A'}}
+template <typename T> A(T)->B<T>;         // expected-error {{deduced type 'B<T>' (aka 'A<T>') of deduction guide is not written as a specialization of template 'A'}}
 template<typename T> A(T*) -> const A<T>; // expected-error {{deduced type 'const A<T>' of deduction guide is not a specialization of template 'A'}}
 
 // A deduction-guide shall be declared in the same scope as the corresponding
diff --git a/clang/test/Misc/diag-template-diffing-cxx11.cpp b/clang/test/Misc/diag-template-diffing-cxx11.cpp
index eefeb0b1117c7..c32a3fd56fc1b 100644
--- a/clang/test/Misc/diag-template-diffing-cxx11.cpp
+++ b/clang/test/Misc/diag-template-diffing-cxx11.cpp
@@ -257,25 +257,22 @@ int f9(S9<int, char, U9<const double>>);
 int k9 = f9(V9<double>());
 
 // CHECK-ELIDE-NOTREE: no matching function for call to 'f9'
-// CHECK-ELIDE-NOTREE: candidate function not viable: no known conversion from 'S9<[2 * ...], S9<[2 * ...], double>>' to 'S9<[2 * ...], S9<[2 * ...], const double>>' for 1st argument
+// CHECK-ELIDE-NOTREE: candidate function not viable: no known conversion from 'S9<[2 * ...], U9<double>>' to 'S9<[2 * ...], U9<const double>>' for 1st argument
 // CHECK-NOELIDE-NOTREE: no matching function for call to 'f9'
-// CHECK-NOELIDE-NOTREE: candidate function not viable: no known conversion from 'S9<int, char, S9<int, char, double>>' to 'S9<int, char, S9<int, char, const double>>' for 1st argument
+// CHECK-NOELIDE-NOTREE: candidate function not viable: no known conversion from 'S9<int, char, U9<double>>' to 'S9<int, char, U9<const double>>' for 1st argument
 // CHECK-ELIDE-TREE: no matching function for call to 'f9'
 // CHECK-ELIDE-TREE: candidate function not viable: no known conversion from argument type to parameter type for 1st argument
 // CHECK-ELIDE-TREE:   S9<
-// CHECK-ELIDE-TREE:     [2 * ...], 
-// CHECK-ELIDE-TREE:     S9<
-// CHECK-ELIDE-TREE:       [2 * ...], 
-// CHECK-ELIDE-TREE:       [double != const double]>>
+// CHECK-ELIDE-TREE:     [2 * ...],
+// CHECK-ELIDE-TREE:     U9<
+// CHECK-ELIDE-TREE:       [(no qualifiers) != const] double>>
 // CHECK-NOELIDE-TREE: no matching function for call to 'f9'
 // CHECK-NOELIDE-TREE: candidate function not viable: no known conversion from argument type to parameter type for 1st argument
 // CHECK-NOELIDE-TREE:   S9<
-// CHECK-NOELIDE-TREE:     int, 
-// CHECK-NOELIDE-TREE:     char, 
-// CHECK-NOELIDE-TREE:     S9<
-// CHECK-NOELIDE-TREE:       int, 
-// CHECK-NOELIDE-TREE:       char, 
-// CHECK-NOELIDE-TREE:       [double != const double]>>
+// CHECK-NOELIDE-TREE:     int,
+// CHECK-NOELIDE-TREE:     char,
+// CHECK-NOELIDE-TREE:     U9<
+// CHECK-NOELIDE-TREE:       [(no qualifiers) != const] double>>
 
 template<typename ...A> class class_types {};
 void set10(class_types<int, int>) {}
diff --git a/clang/test/SemaCXX/nullability.cpp b/clang/test/SemaCXX/nullability.cpp
index d52ba4efaccdb..ca45a0d26617b 100644
--- a/clang/test/SemaCXX/nullability.cpp
+++ b/clang/test/SemaCXX/nullability.cpp
@@ -98,7 +98,7 @@ void test_accepts_nonnull_null_pointer_literal(X *x) {
   accepts_nonnull_6(nullptr); // expected-warning{{null passed to a callee that requires a non-null argument}}
 }
 
-template<void FP(_Nonnull int*)> 
+template<void FP(_Nonnull int*)>
 void test_accepts_nonnull_null_pointer_literal_template() {
   FP(0); // expected-warning{{null passed to a callee that requires a non-null argument}}
 }
@@ -197,6 +197,6 @@ void testNullabilityCompletenessWithTemplate() {
 
 namespace GH60344 {
 class a;
-template <typename b> using c = b _Nullable; // expected-error {{'_Nullable' cannot be applied to non-pointer type 'GH60344::a'}}
+template <typename b> using c = b _Nullable; // expected-error {{'_Nullable' cannot be applied to non-pointer type 'a'}}
 c<a>;  // expected-note {{in instantiation of template type alias 'c' requested here}}
 }
diff --git a/clang/test/SemaCXX/sizeless-1.cpp b/clang/test/SemaCXX/sizeless-1.cpp
index 368a3eeb6955b..688bbf058e4ca 100644
--- a/clang/test/SemaCXX/sizeless-1.cpp
+++ b/clang/test/SemaCXX/sizeless-1.cpp
@@ -325,7 +325,7 @@ void template_fn_rvalue_ref(T &&) {}
 
 #if __cplusplus >= 201103L
 template <typename T>
-using array_alias = T[1]; // expected-error {{array has sizeless element type '__SVInt8_t'}}
+using array_alias = T[1]; // expected-error {{array has sizeless element type 'svint8_t' (aka '__SVInt8_t')}}
 extern array_alias<int> *array_alias_int_ptr;
 extern array_alias<svint8_t> *array_alias_int8_ptr; // expected-note {{in instantiation of template type alias 'array_alias' requested here}}
 #endif
diff --git a/clang/test/SemaHLSL/VectorOverloadResolution.hlsl b/clang/test/SemaHLSL/VectorOverloadResolution.hlsl
index 37d5068c3067c..b320abdd81182 100644
--- a/clang/test/SemaHLSL/VectorOverloadResolution.hlsl
+++ b/clang/test/SemaHLSL/VectorOverloadResolution.hlsl
@@ -22,7 +22,7 @@ void Fn2(int16_t2 S);
 // CHECK: CallExpr {{.*}} 'void'
 // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(int64_t2)' <FunctionToPointerDecay>
 // CHECK-NEXT: DeclRefExpr {{.*}} 'void (int64_t2)' lvalue Function {{.*}} 'Fn2' 'void (int64_t2)'
-// CHECK-NEXT: ImplicitCastExpr {{.*}} 'vector<long, 2>' <IntegralCast>
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'vector<int64_t, 2>' <IntegralCast>
 // CHECK-NEXT: ImplicitCastExpr {{.*}} 'int2':'vector<int, 2>' <LValueToRValue>
 // CHECK-NEXT: DeclRefExpr {{.*}} 'int2':'vector<int, 2>' lvalue ParmVar {{.*}} 'I' 'int2':'vector<int, 2>'
 
@@ -36,7 +36,7 @@ void Fn3( int64_t2 p0);
 // CHECK: CallExpr {{.*}} 'void'
 // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(int64_t2)' <FunctionToPointerDecay>
 // CHECK-NEXT: DeclRefExpr {{.*}} 'void (int64_t2)' lvalue Function {{.*}} 'Fn3' 'void (int64_t2)'
-// CHECK-NEXT: ImplicitCastExpr {{.*}} 'vector<long, 2>' <FloatingToIntegral>
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'vector<int64_t, 2>' <FloatingToIntegral>
 // CHECK-NEXT: ImplicitCastExpr {{.*}} 'half2':'vector<half, 2>' <LValueToRValue>
 // CHECK-NEXT: DeclRefExpr {{.*}} 'half2':'vector<half, 2>' lvalue ParmVar {{.*}} 'p0' 'half2':'vector<half, 2>'
 // CHECKIR-LABEL: Call3
@@ -49,7 +49,7 @@ void Call3(half2 p0) {
 // CHECK: CallExpr {{.*}} 'void'
 // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(int64_t2)' <FunctionToPointerDecay>
 // CHECK-NEXT: DeclRefExpr {{.*}} 'void (int64_t2)' lvalue Function {{.*}} 'Fn3' 'void (int64_t2)'
-// CHECK-NEXT: ImplicitCastExpr {{.*}} 'vector<long, 2>' <FloatingToIntegral>
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'vector<int64_t, 2>' <FloatingToIntegral>
 // CHECK-NEXT: ImplicitCastExpr {{.*}} 'float2':'vector<float, 2>' <LValueToRValue>
 // CHECK-NEXT: DeclRefExpr {{.*}} 'float2':'vector<float, 2>' lvalue ParmVar {{.*}} 'p0' 'float2':'vector<float, 2>'
 // CHECKIR-LABEL: Call4
@@ -65,8 +65,8 @@ void Fn4( float2 p0);
 // CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(float2)' <FunctionToPointerDecay>
 // CHECK-NEXT: DeclRefExpr {{.*}} 'void (float2)' lvalue Function {{.*}} 'Fn4' 'void (float2)'
 // CHECK-NEXT: ImplicitCastExpr {{.*}} 'vector<float, 2>' <IntegralToFloating>
-// CHECK-NEXT: ImplicitCastExpr {{.*}} 'int64_t2':'vector<long, 2>' <LValueToRValue>
-// CHECK-NEXT: DeclRefExpr {{.*}} 'int64_t2':'vector<long, 2>' lvalue ParmVar {{.*}} 'p0' 'int64_t2':'vector<long, 2>'
+// CHECK-NEXT: ImplicitCastExpr {{.*}} 'int64_t2':'vector<int64_t, 2>' <LValueToRValue>
+// CHECK-NEXT: DeclRefExpr {{.*}} 'int64_t2':'vector<int64_t, 2>' lvalue ParmVar {{.*}} 'p0' 'int64_t2':'vector<int64_t, 2>'
 // CHECKIR-LABEL: Call5
 // CHECKIR: {{.*}} = sitofp <2 x i64> {{.*}} to <2 x float>
 void Call5(int64_t2 p0) {
diff --git a/clang/test/SemaTemplate/deduction-guide.cpp b/clang/test/SemaTemplate/deduction-guide.cpp
index 043a4ccf56e36..d03c783313dd7 100644
--- a/clang/test/SemaTemplate/deduction-guide.cpp
+++ b/clang/test/SemaTemplate/deduction-guide.cpp
@@ -161,10 +161,10 @@ using DT = D<int, int>;
 // CHECK:               `-SubstTemplateTypeParmPackType {{.*}} 'U' dependent contains_unexpanded_pack typename depth 1 index 0 ... U
 // CHECK:                 |-TypeAliasTemplate {{.*}} 'B'
 // CHECK:                 `-TemplateArgument pack
-// CHECK:                   |-TemplateArgument type 'type-parameter-0-1'
+// CHECK:                   |-TemplateArgument type 'U1':'type-parameter-0-1'
 // CHECK-NOT: Subst
 // CHECK:                   | `-TemplateTypeParmType
-// CHECK:                   `-TemplateArgument type 'type-parameter-0-2'
+// CHECK:                   `-TemplateArgument type 'U2':'type-parameter-0-2'
 // CHECK-NOT: Subst
 // CHECK:                     `-TemplateTypeParmType
 
@@ -351,9 +351,9 @@ X x = {{1, 2}};
 // CHECK-LABEL: Dumping GH64625::<deduction guide for X>:
 // CHECK-NEXT: FunctionTemplateDecl {{.+}} <{{.+}}:[[#@LINE - 7]]:1, col:27> col:27 implicit <deduction guide for X>
 // CHECK-NEXT: |-TemplateTypeParmDecl {{.+}} <col:11, col:17> col:17 referenced class depth 0 index 0 T
-// CHECK:      |-CXXDeductionGuideDecl {{.+}} <col:27> col:27 implicit <deduction guide for X> 'auto (T (&&)[2]) -> X<T>' aggregate 
+// CHECK:      |-CXXDeductionGuideDecl {{.+}} <col:27> col:27 implicit <deduction guide for X> 'auto (T (&&)[2]) -> X<T>' aggregate
 // CHECK-NEXT: | `-ParmVarDecl {{.+}} <col:27> col:27 'T (&&)[2]'
-// CHECK-NEXT: `-CXXDeductionGuideDecl {{.+}} <col:27> col:27 implicit used <deduction guide for X> 'auto (int (&&)[2]) -> GH64625::X<int>' implicit_instantiation aggregate 
+// CHECK-NEXT: `-CXXDeductionGuideDecl {{.+}} <col:27> col:27 implicit used <deduction guide for X> 'auto (int (&&)[2]) -> GH64625::X<int>' implicit_instantiation aggregate
 // CHECK-NEXT:  |-TemplateArgument type 'int'
 // CHECK-NEXT:  | `-BuiltinType {{.+}} 'int'
 // CHECK-NEXT:  `-ParmVarDecl {{.+}} <col:27> col:27 'int (&&)[2]'
@@ -361,7 +361,7 @@ X x = {{1, 2}};
 // CHECK-NEXT: |-InjectedClassNameType {{.+}} 'X<T>' dependent
 // CHECK-NEXT: | `-CXXRecord {{.+}} 'X'
 // CHECK-NEXT: `-RValueReferenceType {{.+}} 'T (&&)[2]' dependent
-// CHECK-NEXT:  `-ConstantArrayType {{.+}} 'T[2]' dependent 2 
+// CHECK-NEXT:  `-ConstantArrayType {{.+}} 'T[2]' dependent 2
 // CHECK-NEXT:    `-TemplateTypeParmType {{.+}} 'T' dependent depth 0 index 0
 // CHECK-NEXT:      `-TemplateTypeParm {{.+}} 'T'
 
@@ -372,37 +372,37 @@ template <class T, class U> struct TwoArrays {
 
 TwoArrays ta = {{1, 2}, {3, 4, 5}};
 // CHECK-LABEL: Dumping GH64625::<deduction guide for TwoArrays>:
-// CHECK-NEXT: FunctionTemplateDecl {{.+}} <{{.+}}:[[#@LINE - 7]]:1, col:36> col:36 implicit <deduction guide for TwoArrays> 
-// CHECK-NEXT: |-TemplateTypeParmDecl {{.+}} <col:11, col:17> col:17 referenced class depth 0 index 0 T 
-// CHECK-NEXT: |-TemplateTypeParmDecl {{.+}} <col:20, col:26> col:26 referenced class depth 0 index 1 U 
-// CHECK:      |-CXXDeductionGuideDecl {{.+}} <col:36> col:36 implicit <deduction guide for TwoArrays> 'auto (T (&&)[2], U (&&)[3]) -> TwoArrays<T, U>' aggregate  
-// CHECK-NEXT: | |-ParmVarDecl {{.+}} <col:36> col:36 'T (&&)[2]' 
-// CHECK-NEXT: | `-ParmVarDecl {{.+}} <col:36> col:36 'U (&&)[3]' 
-// CHECK-NEXT: `-CXXDeductionGuideDecl {{.+}} <col:36> col:36 implicit used <deduction guide for TwoArrays> 'auto (int (&&)[2], int (&&)[3]) -> GH64625::TwoArrays<int, int>' implicit_instantiation aggregate  
-// CHECK-NEXT:   |-TemplateArgument type 'int' 
-// CHECK-NEXT:   | `-BuiltinType {{.+}} 'int' 
-// CHECK-NEXT:   |-TemplateArgument type 'int' 
-// CHECK-NEXT:   | `-BuiltinType {{.+}} 'int' 
-// CHECK-NEXT:   |-ParmVarDecl {{.+}} <col:36> col:36 'int (&&)[2]' 
-// CHECK-NEXT:   `-ParmVarDecl {{.+}} <col:36> col:36 'int (&&)[3]' 
-// CHECK-NEXT: FunctionProtoType {{.+}} 'auto (T (&&)[2], U (&&)[3]) -> TwoArrays<T, U>' dependent trailing_return 
-// CHECK-NEXT: |-InjectedClassNameType {{.+}} 'TwoArrays<T, U>' dependent 
-// CHECK-NEXT: | `-CXXRecord {{.+}} 'TwoArrays' 
-// CHECK-NEXT: |-RValueReferenceType {{.+}} 'T (&&)[2]' dependent 
-// CHECK-NEXT: | `-ConstantArrayType {{.+}} 'T[2]' dependent 2  
-// CHECK-NEXT: |   `-TemplateTypeParmType {{.+}} 'T' dependent depth 0 index 0 
-// CHECK-NEXT: |     `-TemplateTypeParm {{.+}} 'T' 
-// CHECK-NEXT: `-RValueReferenceType {{.+}} 'U (&&)[3]' dependent 
-// CHECK-NEXT:   `-ConstantArrayType {{.+}} 'U[3]' dependent 3  
-// CHECK-NEXT:     `-TemplateTypeParmType {{.+}} 'U' dependent depth 0 index 1 
+// CHECK-NEXT: FunctionTemplateDecl {{.+}} <{{.+}}:[[#@LINE - 7]]:1, col:36> col:36 implicit <deduction guide for TwoArrays>
+// CHECK-NEXT: |-TemplateTypeParmDecl {{.+}} <col:11, col:17> col:17 referenced class depth 0 index 0 T
+// CHECK-NEXT: |-TemplateTypeParmDecl {{.+}} <col:20, col:26> col:26 referenced class depth 0 index 1 U
+// CHECK:      |-CXXDeductionGuideDecl {{.+}} <col:36> col:36 implicit <deduction guide for TwoArrays> 'auto (T (&&)[2], U (&&)[3]) -> TwoArrays<T, U>' aggregate
+// CHECK-NEXT: | |-ParmVarDecl {{.+}} <col:36> col:36 'T (&&)[2]'
+// CHECK-NEXT: | `-ParmVarDecl {{.+}} <col:36> col:36 'U (&&)[3]'
+// CHECK-NEXT: `-CXXDeductionGuideDecl {{.+}} <col:36> col:36 impli...
[truncated]

@mizvekov
Copy link
Contributor Author

mizvekov commented Aug 4, 2024

@alexfh I can't observe anymore the performance regression you reported on the original phab DR (q2.cc test case).

The original reduction doesn't compile anymore starting from clang-18, so I took the same base test and applied it to current libc++, but the memory usage and wall time are pretty close.

Let me know if you have an additional test case.

Copy link
Contributor

@cor3ntin cor3ntin left a comment

Choose a reason for hiding this comment

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

Given that we are very early in the cycle, i think it makes sense to reland that and see if it stick.
Can you add a release note though ?

@mizvekov mizvekov force-pushed the users/mizvekov/clang-sugar-substitute-concepts branch from 9919bb4 to 6a172a9 Compare August 4, 2024 22:48
@mizvekov mizvekov requested a review from a team as a code owner August 4, 2024 22:48
Base automatically changed from users/mizvekov/clang-sugar-substitute-concepts to main August 5, 2024 01:11
This makes use of the changes introduced in D134604, in order to
instantiate alias templates witn a final sugared substitution.

This comes at no additional relevant cost.
Since we don't track / unique them in specializations, we wouldn't be
able to resugar them later anyway.

Differential Revision: https://reviews.llvm.org/D136565
@mizvekov mizvekov force-pushed the users/mizvekov/clang-sugar-substitute-template-alias branch from 4615439 to 1c6bfce Compare August 5, 2024 01:31
@mizvekov mizvekov removed the request for review from a team August 5, 2024 01:32
@mizvekov mizvekov merged commit 7f78f99 into main Aug 5, 2024
8 checks passed
@mizvekov mizvekov deleted the users/mizvekov/clang-sugar-substitute-template-alias branch August 5, 2024 02:28
@bolshakov-a
Copy link
Contributor

When instantiating such an alias:

template <typename T>
using Identity = T;

before the patch:

TemplateSpecializationType 0x5555676f0a60 'Identity<IndirectClass>' sugar alias
|-name: 'Identity' qualified
| `-TypeAliasTemplateDecl 0x5555676c91d8  Identity
|-TemplateArgument type 'IndirectClass'
| `-ElaboratedType 0x5555676c9250 'IndirectClass' sugar
|   `-RecordType 0x5555676c8430 'class IndirectClass'
|     `-CXXRecord 0x5555676c8398 'IndirectClass'
`-SubstTemplateTypeParmType 0x5555676f0a20 'class IndirectClass' sugar typename depth 0 index 0 T
  |-TypeAliasTemplate 0x5555676c91d8 'Identity'
  `-RecordType 0x5555676c8430 'class IndirectClass'
    `-CXXRecord 0x5555676c8398 'IndirectClass'

After the patch:

TemplateSpecializationType 0x5555676f0a30 'Identity<IndirectClass>' sugar alias
|-name: 'Identity' qualified
| `-TypeAliasTemplateDecl 0x5555676c91d8  Identity
|-TemplateArgument type 'IndirectClass'
| `-ElaboratedType 0x5555676c9250 'IndirectClass' sugar
|   `-RecordType 0x5555676c8430 'class IndirectClass'
|     `-CXXRecord 0x5555676c8398 'IndirectClass'
`-ElaboratedType 0x5555676c9250 'IndirectClass' sugar
  `-RecordType 0x5555676c8430 'class IndirectClass'
    `-CXXRecord 0x5555676c8398 'IndirectClass'

@mizvekov, do you have any idea how to get back the lost SubstTemplateTypeParmType? It plays an important role in the IWYU tool analysis. Thanks!

@mizvekov
Copy link
Contributor Author

mizvekov commented Aug 5, 2024

@mizvekov, do you have any idea how to get back the lost SubstTemplateTypeParmType? It plays an important role in the IWYU tool analysis. Thanks!

So from my undertstanding, IWYU only needs the SubstTemplateTypeParmType for resugaring purposes, in order to recover the type as written by the user.

But with this patch we are doing the substitution already with sugar, so there is no need to resugar, so IWYU shouldn't need the SubstTemplateTypeParmType here anymore.

Does that make sense?

@bolshakov-a
Copy link
Contributor

No, IWYU has some complex logic to figure out which type components the type alias author intends to provide, and which should be #included at the use site. Of course, substituted template type arguments are the user responsibility, not the alias author's one, hence this information is important.

@mizvekov
Copy link
Contributor Author

mizvekov commented Aug 5, 2024

The basic premise here was implemented back in D134604, and this has been for a few years applied in some cases, like substitution of default arguments.

We leave a Subst* node behind with the purpose of somewhere down the line changing it back to what the user wrote.

So we don't leave a Subst* node behind when we don't need to resugar, or in the future when we implement clang's own resugarer, we wouldn't leave it behind after resugaring as well.

This reduces the cost of this sugared substitution and resugaring, and would help achieving the goal of merging our own resugarer in the future.

From past conversations with IWYU maintainer, this was desirable, since IWYU rolls its own resugarer, which is faulty and difficult to maintain.

Now this other use case was never put forward before, and it looks like there might be some alternative way of doing this.

@mizvekov
Copy link
Contributor Author

mizvekov commented Aug 5, 2024

One possibility here is that if you have the type, then you will have a template specialization type for the template alias, and that gives you the template arguments used to specialize the alias. Which should answer this need.

But of course, if you have a lossy semantic adjustment which makes you lose top level sugar, then you might not have the template specialization type anymore, but this will also apply to Subst* nodes, which can also be lost in these cases.

But you might have noticed already that type sugar cannot be reliably depended upon.

@zygoloid thoughts?

@bolshakov-a
Copy link
Contributor

One possibility here is that if you have the type, then you will have a template specialization type for the template alias, and that gives you the template arguments used to specialize the alias. Which should answer this need.

This can probably go, thank you!

@gribozavr
Copy link
Collaborator

gribozavr commented Aug 6, 2024

So from my undertstanding, IWYU only needs the SubstTemplateTypeParmType for resugaring purposes, in order to recover the type as written by the user.

But with this patch we are doing the substitution already with sugar, so there is no need to resugar, so IWYU shouldn't need the SubstTemplateTypeParmType here anymore.

I agree that we don't need SubstTemplateTypeParmType nodes if all resuraging that we ever do is related to types that the Clang frontend itself knows. However that is not universally true.

For example, we (Google) have a tool for inferring and checking nullability contracts (https://github.com/google/crubit/tree/main/nullability), and a tool for lifetime contracts (https://github.com/google/crubit/tree/main/lifetime_annotations). These tool rely on SubstTemplateTypeParmType nodes to propagate nullability and lifetime annotations through template signatures. We can't rely on the Clang frontend doing this propagation because this logic is in an external tool, and the whole notion of nullability and lifetimes is absent in Clang's upstream repository.

So for all practical purposes, the removal of SubstTemplateTypeParmType is losing relevant information, and it means that no other tool can do the things that Clang frontend can.

To be clear: our tool right now is partially broken (when alias templates are involved), and I am not sure how to fix it when SubstTemplateTypeParmType nodes are not going to be available.

@mizvekov
Copy link
Contributor Author

mizvekov commented Aug 6, 2024

I agree that we don't need SubstTemplateTypeParmType nodes if all resuraging that we ever do is related to types that the Clang frontend itself knows. However that is not universally true.

For example, we (Google) have a tool for inferring and checking nullability contracts (https://github.com/google/crubit/tree/main/nullability), and a tool for lifetime contracts (https://github.com/google/crubit/tree/main/lifetime_annotations). These tool rely on SubstTemplateTypeParmType nodes to propagate nullability and lifetime annotations through template signatures. We can't rely on the Clang frontend doing this propagation because this logic is in an external tool, and the whole notion of nullability and lifetimes is absent in Clang's upstream repository.

Can we extend Clang in other ways where this propagation can happen within our transforms?

To be clear: our tool right now is partially broken (when alias templates are involved), and I am not sure how to fix it when SubstTemplateTypeParmType nodes are not going to be available.

To be clear, it has been broken as of a couple of years already for some cases, we already perform this 'Final' substitution when instantiating default arguments for templates, for example.

This patch extends that to alias templates.

As for my first question, can you think of an alternative way we can go about this? What would you need to be upstreamed in order for our own transforms to handle this for you?

It sounds like it will be a lot of waste if we have our own resugarer within clang, but then you would be forced to implement your own external resugarer anyway.

@gribozavr
Copy link
Collaborator

gribozavr commented Aug 7, 2024

Can we extend Clang in other ways where this propagation can happen within our transforms?

I don't know, but it would be a lot of work, and would likely still mean preserving the same information as SubstTemplateTypeParmType nodes currently provide.

Our scope is also larger than what Clang's resugarer aims to do, because for our purposes sometimes there isn't even one correct sugar to be permanently recorded in the AST. For example, we also want to propagate our annotations through instantiated function template bodies so that we can analyze function template instantiations in a context-sensitive way. For example:

template<typename Container, typename E>
void Push(Container &xs, E elt) {
  xs.push_back(std::move(elt));
}

void Caller(
  std::vector<absl::Nullable<int*>> nullable_pointers,
  std::vector<absl::Nonnull<int*>> nonnull_pointers
) {
  Push(nullable_pointers, nullptr); // ok
  Push(nonnull_pointers, nullptr); // warning inside of 'Push' with a template instantiation stack trace
}

To be clear, it has been broken as of a couple of years already for some cases, we already perform this 'Final' substitution when instantiating default arguments for templates, for example.

Yes, and we consider those cases to be bugs, we just didn't get to them yet...

As for my first question, can you think of an alternative way we can go about this? What would you need to be upstreamed in order for our own transforms to handle this for you?

PTAL at https://github.com/google/crubit/blob/main/nullability/type_nullability.cc, especially the NullabilityWalker and Rebuilder abstractions to get a rough idea of what we're doing. While the code is complex, it is reflecting the nature of the problem.

However if upstream Clang provided base classes that only did this, without exposing the underlying data, we would lose out on flexibility. I'm also not sure that the resugaring algorithms are exactly the same between nullability and lifetime analysis.

The alternatives I can think of are all much worse than leaving SubstTemplateTypeParmType nodes and admitting that they are public API, and these alternatives don't necessarily allow us to remove SubstTemplateTypeParmType or some other equivalent marker anyway. For example, adding a more general API for pluggable type system extensions; such facility is quite nebulous and would either compromise on flexibility (e.g., would only propagate C++11-style attributes on a best-effort basis), or would be flexible but cost too much complexity in Clang's core for the benefit of only a few out-of-tree users.

Note that we are hoping to eventually upstream some of our work (see, for example, our RFC thread on lifetimes
[RFC] Lifetime annotations for C++
), but confidently proposing a complex type system extension like this requires having solid implementation and usage experience, which in our case would come from Google-internal deployment. If the extension points for out-of-tree usage get removed and we are forced to move our code upstream before we actually understand what a good design would be, arguably everyone would be worse off.

@mizvekov
Copy link
Contributor Author

mizvekov commented Aug 7, 2024

I don't know, but it would be a lot of work, and would likely still mean preserving the same information as SubstTemplateTypeParmType nodes currently provide.

Our scope is also larger than what Clang's resugarer aims to do, because for our purposes sometimes there isn't even one correct sugar to be permanently recorded in the AST. For example, we also want to propagate our annotations through instantiated function template bodies so that we can analyze function template instantiations in a context-sensitive way. For example:

I wouldn't say that's outside the scope of clang's resugarer. I just don't think that can be something we can always enable,
as that would make some examples of source code take exponentially more time and memory to compile.

Right now the priority is to implement the stuff that won't take a big hit on perf. But that is just priority.
Clang's resugarer can still get there first, even if that's lower relative priority.

Yes, and we consider those cases to be bugs, we just didn't get to them yet...

I don't think, from looking at the resugarer implemented in type_nullability.cc, it's anywhere close to being able
to resugar through a complex type like an STL vector, as shown in the example.

I think the first thing you would find you need is to get those default arguments resugared, as for example both libc++ and libstdc++ vector implementations derive the element type not through the first template argument, but through the allocator instead.

As for my first question, can you think of an alternative way we can go about this? What would you need to be upstreamed in order for our own transforms to handle this for you?

PTAL at https://github.com/google/crubit/blob/main/nullability/type_nullability.cc, especially the NullabilityWalker and Rebuilder abstractions to get a rough idea of what we're doing. While the code is complex, it is reflecting the nature of the problem.

I see the implementation of the resugarer in type_nullability.cc, and it's much more incomplete that the one I am proposing to upstream. So I think if you get it to work through upstream transforms, you will get there faster.

However if upstream Clang provided base classes that only did this, without exposing the underlying data, we would lose out on flexibility. I'm also not sure that the resugaring algorithms are exactly the same between nullability and lifetime analysis.

The alternatives I can think of are all much worse than leaving SubstTemplateTypeParmType nodes and admitting that they are public API, and these alternatives don't necessarily allow us to remove SubstTemplateTypeParmType or some other equivalent marker anyway. For example, adding a more general API for pluggable type system extensions; such facility is quite nebulous and would either compromise on flexibility (e.g., would only propagate C++11-style attributes on a best-effort basis), or would be flexible but cost too much complexity in Clang's core for the benefit of only a few out-of-tree users.

Can you explain in more concrete terms how you think the sugared substitution as implemented in this patch creates impediment relative to what you need in your project?

As far as I can see, you don't need to keep the Subst* nodes after resugaring if you implement:

  • AST Type sugar nodes which represent nullability and lifetime annotations.
  • Teach getCommonSugarType how to fold these annotations. This will implement what should happen when for example different lifetimes appear in each arm of a ternary operator, or each return statement of a function with deduced return type.

So then our transforms should still do the right things you need for them.

You would still need to implement the semantic analysis for these annotations, even if externally. But that still should give a lot of flexibility.

Note that we are hoping to eventually upstream some of our work (see, for example, our RFC thread on lifetimes [RFC] Lifetime annotations for C++), but confidently proposing a complex type system extension like this requires having solid implementation and usage experience, which in our case would come from Google-internal deployment. If the extension points for out-of-tree usage get removed and we are forced to move our code upstream before we actually understand what a good design would be, arguably everyone would be worse off.

I don't think the bar is that high for starting to upstream the work. As long as this is marked as an experimental extension that must be manually enabled, you can still treat this as a Greenfield thing, and do your experiments.

I was aware of the lifetime annotations thread, I even commented on it.
I informed about my resugaring work, which google helped finance by the way.

@gribozavr
Copy link
Collaborator

On resugaring bodies of function template instantiations

I just don't think that can be something we can always enable, as that would make some examples of source code take exponentially more time and memory to compile.

We keep track of propagated and inferred nullability annotations in a side data structure. Our approach allows us to save on memory by not actually re-instantiating everything with new sugar.

But I do have a question - how is the built-in resugarer going to do this without maintaining a side data structure like we do? Clang currently does not have a concept of non-canonical instantiations. std::vector<int*>, std::vector<absl::Nullable<int*>>, std::vector<absl::Nonnull<int*>> are the same canonical type, so they must share one instantiation. Are you also proposing to change that?

Right now the priority is to implement the stuff that won't take a big hit on perf. But that is just priority.

But wouldn't it be better if out-of-tree users like us were not blocked on the priorities of your work? With the Subst* nodes in the AST the information is available for out-of-tree users to use in any way they like. Are you sure you can predict all use cases? Do you really want to be in the business of satisfying them?


On (in)completeness of the out-of-tree resugarer

I don't think, from looking at the resugarer implemented in type_nullability.cc, it's anywhere close to being able to resugar through a complex type like an STL vector, as shown in the example.

Actually, resugaring in nullability analysis already works for std::vector::push_back():

#include <vector>
#include "absl/base/nullability.h"

void Test() {
  int *p = nullptr;
  std::vector<absl::Nonnull<int*>> xs;
  xs.push_back(p);
}
$ clang-tidy example.cc
example.cc:7:16: warning: expected parameter '__x' of 'std::vector<int *>::push_back' to be nonnull, but a nullable argument was used [google-runtime-pointer-nullability]
    7 |   xs.push_back(p);
      |                ^

as for example both libc++ and libstdc++ vector implementations derive the element type not through the first template argument, but through the allocator instead.

As far as I see, libc++ does not actually use the allocator to define const_reference, value_type and so on:

template <class _Tp, class _Allocator /* = allocator<_Tp> */>
class _LIBCPP_TEMPLATE_VIS vector {
  typedef _Tp value_type;
  typedef value_type& reference;
  typedef const value_type& const_reference;

  /* ... */ void push_back(const_reference __x);

(see https://github.com/llvm/llvm-project/blob/main/libcxx/include/vector)

I see the implementation of the resugarer in type_nullability.cc, and it's much more incomplete that the one I am proposing to upstream. So I think if you get it to work through upstream transforms, you will get there faster.

I'm not sure what you mean by "get there faster" - our implementation already works, is already deployed internally, and covers a lot of cases that are relevant in practice. We are still tweaking things of course, and we are working on inference tooling (that infers nullability contracts based on the implementation and suggests edits to the source code accordingly).

That is, the difficult part and the focus at the moment is not resugaring in the analysis. I do admit that our implementation of resugaring is probably not as comprehensive as the one that you're working on, but it works for the vast majority of real-world code that people write in our monorepo - we tweaked it based on the cases that we actually observed.


On upstreaming the work

As far as I can see, you don't need to keep the Subst* nodes after resugaring if you implement:

We are in the process of adding Clang's _Nullable and _Nonnull attributes to the implementation of absl::Nullable and absl::Nonnull. This work is taking time because we need to adjust our internal codebase to conform to Clang's existing expectations, behaviors, and warnings that get triggered by these attributes. These attributes will get picked up by the in-tree resugaring implementation because the source of the annotation is physically spelled in the code. Good.

However, a part of the nullability language extension is the pragma that changes the default meaning of pointers from "unknown" to "nonnull". That is, we interpret the following two files in an identical way:

#include "absl/base/nullability.h"

#pragma nullability file_default nonnull

void TakePointer(int *p);
#include "absl/base/nullability.h"

void TakePointer(absl::Nonnull<int *> p);

Unfortunately we expect this pragma to be a controversial upstream because Clang already has a pragma for this exact purpose (#pragma clang assume_nonnull begin/end), but it was introduced by Apple in context of Objective-C and some of its behavior is incompatible with C++. It is long story and the issues are subtle, this is not the right place to discuss it.

In the end, we need a separate pragma with slightly different semantics because Clang's pragma was not suitable for C++. Getting these differences reconciled can take months in discussions alone, and we don't want to spend time on this when we are still not 100% sure that the model is going to work for us in the long run.


Fundamentally, your proposal to reuse the built-in resugarer depends on upstreaming an incomplete language extension, which cuts against Clang's extension policy, https://clang.llvm.org/get_involved.html:

Evidence of a significant user community: This is based on a number of factors, including an existing user community

I don't know how to demonstrate a significant user community if the extension must live in the Clang core. Of course we could fork Clang, but we would need to maintain this fork for years. We have been working on nullability since April 2020 - that's 4+ years already. The cost involved in maintaining a Clang fork for 4+ years, compared to an out-of-tree ClangTidy check, is just unpalatable.

We're lucky that Clang upstream already has nullability annotations that happen to be a mostly good match (modulo the pragma and some other issues). But what about our work on lifetimes?


On separating resugaring improvements and Subst* removal

Let me raise the level of the discussion here. We do appreciate your work on improving resugaring. However, simultaneously removing the SubstTemplateTypeParmType is breaking one out-of-tree language extension that is already deployed (nullability) and is blocking research and prototyping on another out-of-tree extension (lifetimes).

I do want to emphasize that nullability is already deployed, we depend on nullability warnings internally, and developers benefit from them greatly. We are still tweaking the semantics though (e.g., we are in the process of introducing the pragma throughout the codebase etc.), and that's why it is not the right time to upstream things yet.

For lifetimes work, the language extension is not yet deployed, but further experimentation and research would be blocked if Subst* nodes are removed.

Is it possible to decouple landing the improvements in resugaring and removal of SubstTemplateTypeParmType? The first you can land immediately, but the second blocks our work. Is it separable?


On Subst* removal and AST fidelity

Could you provide a more detailed rationale for removing SubstTemplateTypeParmType?

Generally Clang tries to preserve information in the AST as much as possible. Isn't removing this node going against this principle?

@mizvekov
Copy link
Contributor Author

mizvekov commented Aug 7, 2024

We keep track of propagated and inferred nullability annotations in a side data structure. Our approach allows us to save on memory by not actually re-instantiating everything with new sugar.

But I do have a question - how is the built-in resugarer going to do this without maintaining a side data structure like we do? Clang currently does not have a concept of non-canonical instantiations. std::vector<int*>, std::vector<absl::Nullable<int*>>, std::vector<absl::Nonnull<int*>> are the same canonical type, so they must share one instantiation. Are you also proposing to change that?

I think there might have been a slight miscommunication here, my fault.

The idea being considered is to just instantiate the template using the first sugaring found, using a sugared non-final substitution. That way, any semantic analysis during template instantiation would be able to produce diagnostics relative to that sugaring. But that would depend on the resugarer being fully implemented, so that any uses of that template specialization would be resugared to the correct sugar relative to the point of use. We would also need to enforce that
a template can't be instantiated using any kind of sugaring which could have a semantic effect, like the gcc alignment attribute when applied to typedef.

As currently things stand, we would have no reason to produce a second instantiation for the same type for a template which already succeeded in instantiation, and which we will resugar later anyway, because that should not produce a different outcome.

But external analysis should be free to pursue that if needed.

But wouldn't it be better if out-of-tree users like us were not blocked on the priorities of your work? With the Subst* nodes in the AST the information is available for out-of-tree users to use in any way they like. Are you sure you can predict all use cases? Do you really want to be in the business of satisfying them?

You are free to propose any AST extension that would help out-of-tree users.

That proposal has to have a reasonable justification.

But one thing I haven't mentioned before is the tradeoffs and reasoning behind the current approach of instantiating alias templates fully sugared, instead of resugaring them later.

The main reason is that as things stood before the current patch, alias templates couldn't be reliably resugared.
We don't track specializations for alias templates, like we do for classes for example.
When we instantiate a class template, any Subst* nodes produced have an AssociatedDecl which references
the class template specialization.
But for alias templates, we don't have a TypeAliasTemplateSpecialziationDecl, so the AssociatedDecl references just the template pattern.

This is a problem because you can run into a type which has Subst nodes from different alias template specializations, and as you can't tell them apart, you could end up producing incorrect resugaring.
This can happen because some operations can lose the template specialization type for the alias templates, which is top level sugar, but the Subst node can remain if its present in the children of a canonical node.

Actually, resugaring in nullability analysis already works for std::vector::push_back():
As far as I see, libc++ does not actually use the allocator to define const_reference, value_type and so on:

You are right, I misremembered, that was true only for libstdc++.

That is, the difficult part and the focus at the moment is not resugaring in the analysis. I do admit that our implementation of resugaring is probably not as comprehensive as the one that you're working on, but it works for the vast majority of real-world code that people write in our monorepo - we tweaked it based on the cases that we actually observed.

I see.
The difficulty is that we want to have an upstream clang resugarer too.
This resugarer would ideally have a negligible performance impact, so that we can always enable it, and all
users would get improved type-as-written in diagnostics without having to do anything else.

We could support external resugarers too, without considering costs, that is not unreasonable.
But if having to support an external resugarer meant we would not be able to afford to have an in-tree one, that seems unreasonable.

I think this patch is the wrong tree to bark on though.
As I said previously, before this patch, instantiation of alias templates did not provide reliable support for resugaring anyway. It might work in limited cases, provided you implement enough workarounds. But the design for it is not right.

Tracking alias template specializations would be a lot more complex and likely higher performance impact.
So it would probably be better if we could find a way your passes could work with this patch.

Fundamentally, your proposal to reuse the built-in resugarer depends on upstreaming an incomplete language extension, which cuts against Clang's extension policy, https://clang.llvm.org/get_involved.html:

Evidence of a significant user community: This is based on a number of factors, including an existing user community

If you are using this internally at google, that seems like evidence that there is an existing user community.

Let me raise the level of the discussion here. We do appreciate your work on improving resugaring. However, simultaneously removing the SubstTemplateTypeParmType is breaking one out-of-tree language extension that is already deployed (nullability) and is blocking research and prototyping on another out-of-tree extension (lifetimes).

In the course of implementing resugaring, there were several deficiencies in upstream clang which had to be corrected.
For example, we didn't have pack indexes, there was no AssociatedDecl, and some other missing things.

I think if I had the idea that I could implement the resugaring completely out-of-tree, these deficiencies would have remained, and consequently work arounds and incomplete resugaring would have to be accepted.

Since you are also using the AssociatedDecl in the type_nnullability analysis, perhaps you have noticed we couldn't
have reliably used just depth and index, like we do for normal template instantiation, because type sugar loss would present a problem.

The type alias is in the same situation here. It's AssociatedDecl is not very useful if it only points to the template pattern.

So I took the course of fixing it in upstream clang, with the aim of having the best outcome for an in-tree resugarer, which is what makes the most sense.

Is it possible to decouple landing the improvements in resugaring and removal of SubstTemplateTypeParmType? The first you can land immediately, but the second blocks our work. Is it separable?

We would still need a distinction between Subst nodes that need to be resugared and those that don't.
Besides the performance impact of keeping these Subst nodes, as they affect uniquing, and they mean there is
more tree traversal needed.

If we want to support out-of-tree resugarers, I think the least bad option here would be perhaps a flag to disable in-tree resugaring.

But that doesn't solve the problem that template type aliases don't have all the necessary design bits for resugaring.
It would be strange if this flag would control what happens there, and also if the flag's on-state would provide a deficient AST, or if we would implement a TypeAliasTemplateSpecializationDecl only for the benefit of external resugarers.

Could you provide a more detailed rationale for removing SubstTemplateTypeParmType?

That's mostly explained above.

Since Subst nodes are currently heavy on sugar, and they can appear deeply nested inside the type, they can have a large impact on uniquing these types.

Since we only need to resugar one Subst node once, we need the distinction between having already handled that type, or not.

For a type alias template, there is currently no declaration that can represent its specialization.

Generally Clang tries to preserve information in the AST as much as possible. Isn't removing this node going against this principle?

As much as possible, within reason, considering performance impact.

There are some things left out because of performance concerns, like:

  • Tracking source locations for qualifiers
  • Semantic adjustment markers for dropping references and qualifiers.
  • Some template argument deduction sugar losses.

@gribozavr
Copy link
Collaborator

@mizvekov I will provide a more detailed response to the points you made in your last message separately, but for now I would like to ask you to revert the commit to unbreak us.

We looked into the problem internally and it does not look we have a simple hack we can apply to recover this information. The solutions you offered so far to unblock us are going to take years to implement (like waiting for you to complete work that you consider low priority, or upstreaming our code - which means upstreaming 20 KLoC of nullability verification code which involves resolving differences around pragma semantics etc.)

bolshakov-a added a commit to bolshakov-a/include-what-you-use that referenced this pull request Aug 8, 2024
llvm/llvm-project@7f78f99 removed
SubstTemplateTypeParmType nodes from an instantiated alias template
type. GetProvidedTypes function relied on them to filter out substituted
argument types. Now, it is done "manually" inside the added
GetAliasTemplateProvidedTypes function, as discussed
in llvm/llvm-project#101858.

When traversing instantiated type alias internals, bare RecordType nodes
appear now instead of ones wrapped in SubstTemplateTypeParmType, hence
the new visiting method added into InstantiatedTemplateVisitor. (Enum
types are not expected to need handling there, hence VisitRecordType
instead of VisitTagType.)

A test case added to cover the change inside GetProvidedTypes function
(i.e. correct handling of intermediate alias templates in a chain).
bolshakov-a added a commit to bolshakov-a/include-what-you-use that referenced this pull request Aug 8, 2024
llvm/llvm-project@7f78f99 removed
SubstTemplateTypeParmType nodes from an instantiated alias template
type. GetProvidedTypes function relied on them to filter out substituted
argument types. Now, it is done "manually" inside the added
GetAliasTemplateProvidedTypes function, as discussed
in llvm/llvm-project#101858.

When traversing instantiated type alias internals, bare RecordType nodes
appear now instead of ones wrapped in SubstTemplateTypeParmType, hence
the new visiting method added into InstantiatedTemplateVisitor. (Enum
types are not expected to need handling there, hence VisitRecordType
instead of VisitTagType.)

A test case added to cover the change inside GetProvidedTypes function
(i.e. correct handling of intermediate alias templates in a chain).
@mizvekov
Copy link
Contributor Author

mizvekov commented Aug 8, 2024

@mizvekov I will provide a more detailed response to the points you made in your last message separately, but for now I would like to ask you to revert the commit to unbreak us.

@gribozavr see #102510 for alternative.

which means upstreaming 20 KLoC of nullability verification code which involves resolving differences around pragma semantics etc.)

That's not really true. You only need to upstream the AST affecting parts. The attributes itself and AST node which represent it.
You can try proposing to add this attribute and guard it under an experimental flag, and promise no stability and to take ownership of it.

@gribozavr
Copy link
Collaborator

@gribozavr see #102510 for alternative.

Thank you! We are looking at this now.

That's not really true. You only need to upstream the AST affecting parts. The attributes itself and AST node which represent it.

The attributes are already upstream (we are reusing nullability attributes added by Apple), as I explained above. The C++-compatible pragma is missing - and I expect it to be a lot of work to reconcile. And even then, we still need to propagate the annotations - which currently happens within the ClangTidy check.

For example, we need to understand that the template parameter 0 of std::vector variable below is nullable:

std::vector<absl::Nullable<int*>> MakeVectorOfNullable();

void Test() {
  std::vector<int*> xs = MakeVectorOfNullable();
  *xs[0]; // warning
}

This propagation could happen within core Clang so that it can be integrated into the resugarer that constructs the AST, but doing so (injecting nullable annotations here) would have its own cost for the AST, and would require rearchitecting the check, to essentially run part of the logic during AST construction instead of inside of ClangTidy.

@bolshakov-a
Copy link
Contributor

bolshakov-a commented Aug 8, 2024

From past conversations with IWYU maintainer, this was desirable, since IWYU rolls its own resugarer, which is faulty and difficult to maintain.

I just want to mention that, actually, IWYU doesn't probably need any resugarer at all. A type template argument as-written is needed just to decide whether its canonical type should be reported for #includeing or not (e.g. because the typedef author has provided it already). Another way to do it is to collect a list of types blocked for reporting before scanning the instantiated template. I started rewriting IWYU in that direction some time ago.

OTOH it is desirable to distinguish between the first and the second argument usages in cases like Template<ClassA, TypedefProvidingClassA>. The bare blocked type list approach cannot achieve that, albeit the current IWYU resugarer cannot as well.

@zyn0217
Copy link
Contributor

zyn0217 commented Aug 9, 2024

or if we would implement a TypeAliasTemplateSpecializationDecl only for the benefit of external resugarers.

It is not only valuable to external resugarers. Another point that warrants an introduction of it is for unevaluated lambdas. These lambdas e.g. appearing as part of the using Alias = decltype(lambda) could carry a requires expression and thus evaluating it requires the recovery of the instantiating template arguments for the type alias, which isn't a quite natural thing without such a Decl. I have improved some of these situations through an on-stack InstantiatingTemplate to help to inspect the template arguments; however, that approach is somewhat cumbersome and still couldn't extend to other scenarios e.g. mangling these lambdas. (#97024. We need a correct DeclContext for the lambda, yet a TypeAliasTemplateDecl isn't a DeclContext, unfortunately.)

bolshakov-a added a commit to bolshakov-a/include-what-you-use that referenced this pull request Aug 10, 2024
llvm/llvm-project@7f78f99 removed
SubstTemplateTypeParmType nodes from an instantiated alias template
type. GetProvidedTypes function relied on them to filter out substituted
argument types. Now, it is done "manually" inside the added
GetAliasTemplateProvidedTypes function, as discussed
in llvm/llvm-project#101858.

When traversing instantiated type alias internals, bare RecordType nodes
appear now instead of ones wrapped in SubstTemplateTypeParmType, hence
the new visiting method added into InstantiatedTemplateVisitor. (Enum
types are not expected to need handling there, hence VisitRecordType
instead of VisitTagType.)

A test case added to cover the change inside GetProvidedTypes function
(i.e. correct handling of intermediate alias templates in a chain).
bolshakov-a added a commit to bolshakov-a/include-what-you-use that referenced this pull request Aug 10, 2024
llvm/llvm-project@7f78f99 removed
SubstTemplateTypeParmType nodes from an instantiated alias template
type. GetProvidedTypes function relied on them to filter out substituted
argument types. Now, it is done "manually" inside the added
GetAliasTemplateProvidedTypes function, as discussed
in llvm/llvm-project#101858.

When traversing instantiated type alias internals, bare RecordType nodes
appear now instead of ones wrapped in SubstTemplateTypeParmType, hence
the new visiting method added into InstantiatedTemplateVisitor. (Enum
types are not expected to need handling there, hence VisitRecordType
instead of VisitTagType.)

A test case added to cover the change inside GetProvidedTypes function
(i.e. correct handling of intermediate alias templates in a chain).
@mizvekov
Copy link
Contributor Author

It is not only valuable to external resugarers. Another point that warrants an introduction of it is for unevaluated lambdas. These lambdas e.g. appearing as part of the using Alias = decltype(lambda) could carry a requires expression and thus evaluating it requires the recovery of the instantiating template arguments for the type alias, which isn't a quite natural thing without such a Decl. I have improved some of these situations through an on-stack InstantiatingTemplate to help to inspect the template arguments; however, that approach is somewhat cumbersome and still couldn't extend to other scenarios e.g. mangling these lambdas. (#97024. We need a correct DeclContext for the lambda, yet a TypeAliasTemplateDecl isn't a DeclContext, unfortunately.)

So in clang we have taken a different approach, in order to not sacrifice template type alias performance, we instead put a Decl context reference in templated declarations that can appear within a template type alias pattern, such as lambdas and blocks.

The idea is a tradeoff where template type alias, which are very light weight, don't get the big performance hit they would otherwise if they had a per-specialization declaration.
But we get a slight performance hit on Lambdas instead, but these are much more heavy weight and need the declcontext anyway for other reasons.
The other tradeoff is increased implementation complexity.

See for example CXXRecordDecl's getLambdaContextDecl function.

Unfortunately there is an issue where we currently only store this lambda context when we need it for mangling reasons, but there are other reasons to depend on it as well, and code in clang currently makes the incorrect assumption that it will always be available when needed.

I am working on a patch to fix that.

@zyn0217
Copy link
Contributor

zyn0217 commented Aug 11, 2024

See for example CXXRecordDecl's getLambdaContextDecl function.
Unfortunately there is an issue where we currently only store this lambda context when we need it for mangling reasons, …

Yeah, I remember we use that to store an ImplicitConceptSpecializationDecl as well, not only for the purpose of mangling but also for lambdas appearing inside a concept. We probably could do the same for the using aliases, though that still needs some tweaking to store template arguments.

kimgr pushed a commit to include-what-you-use/include-what-you-use that referenced this pull request Aug 12, 2024
llvm/llvm-project@7f78f99 removed
SubstTemplateTypeParmType nodes from an instantiated alias template
type. GetProvidedTypes function relied on them to filter out substituted
argument types. Now, it is done "manually" inside the added
GetAliasTemplateProvidedTypes function, as discussed
in llvm/llvm-project#101858.

When traversing instantiated type alias internals, bare RecordType nodes
appear now instead of ones wrapped in SubstTemplateTypeParmType, hence
the new visiting method added into InstantiatedTemplateVisitor. (Enum
types are not expected to need handling there, hence VisitRecordType
instead of VisitTagType.)

A test case added to cover the change inside GetProvidedTypes function
(i.e. correct handling of intermediate alias templates in a chain).
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 clang-tidy clang-tools-extra clangd HLSL HLSL Language Support lldb
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

6 participants