[clang] [HLSL] Implement Texture2D default template (PR #184207)
Steven Perron via cfe-commits
cfe-commits at lists.llvm.org
Fri Mar 6 07:01:23 PST 2026
https://github.com/s-perron updated https://github.com/llvm/llvm-project/pull/184207
>From e6a0e19d7f6682d02fcbb6014bd9bf97b53d9e49 Mon Sep 17 00:00:00 2001
From: Steven Perron <stevenperron at google.com>
Date: Mon, 2 Mar 2026 12:20:09 -0500
Subject: [PATCH 1/3] [HLSL] Implement Texture2D default template
The Texture2D type has a default template of float4. This can be written
in a couple way: `Texture2D<>` or `Texture2D`. This must be implemented
for consistenty with DXC in HLSL202x.
To implement `Texture2D<>` we simply add a default type for the template
parameter.
To implement `Texture2D`, we have to add a special case for a template
type without a template instantiation. For HLSL, we check if it is a
texture type. If so, the default type is filled in.
Note that HLSL202x does not support C++17 Class Template Argument
Deduction, so we cannot use that feature to give us `Texture2D`.
Part of #175630.
Assisted-by: Gemini
---
clang/include/clang/Sema/SemaHLSL.h | 3 ++
clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp | 20 ++++++---
clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h | 3 ++
clang/lib/Sema/HLSLExternalSemaSource.cpp | 5 ++-
clang/lib/Sema/SemaDecl.cpp | 11 +++++
clang/lib/Sema/SemaHLSL.cpp | 43 +++++++++++++++++++
.../AST/HLSL/Texture2D-shorthand-AST.hlsl | 39 +++++++++++++++++
.../Texture2D-default-explicit-binding.hlsl | 24 +++++++++++
.../resources/Texture2D-default.hlsl | 16 +++++++
.../Texture2D-shorthand-contexts.hlsl | 31 +++++++++++++
10 files changed, 189 insertions(+), 6 deletions(-)
create mode 100644 clang/test/AST/HLSL/Texture2D-shorthand-AST.hlsl
create mode 100644 clang/test/CodeGenHLSL/resources/Texture2D-default-explicit-binding.hlsl
create mode 100644 clang/test/CodeGenHLSL/resources/Texture2D-default.hlsl
create mode 100644 clang/test/CodeGenHLSL/resources/Texture2D-shorthand-contexts.hlsl
diff --git a/clang/include/clang/Sema/SemaHLSL.h b/clang/include/clang/Sema/SemaHLSL.h
index a6a38531ac284..6341d3cd1cc00 100644
--- a/clang/include/clang/Sema/SemaHLSL.h
+++ b/clang/include/clang/Sema/SemaHLSL.h
@@ -203,6 +203,9 @@ class SemaHLSL : public SemaBase {
bool CheckCompatibleParameterABI(FunctionDecl *New, FunctionDecl *Old);
+ QualType ActOnTemplateShorthand(TemplateDecl *Template,
+ SourceLocation NameLoc);
+
// Diagnose whether the input ID is uint/unit2/uint3 type.
bool diagnoseInputIDType(QualType T, const ParsedAttr &AL);
bool diagnosePositionType(QualType T, const ParsedAttr &AL);
diff --git a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp
index f99c16c8fe92e..2e535c02e5752 100644
--- a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp
+++ b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp
@@ -1741,9 +1741,8 @@ QualType BuiltinTypeDeclBuilder::getHandleElementType() {
if (Template)
return getFirstTemplateTypeParam();
- if (auto *PartialSpec =
- dyn_cast<ClassTemplatePartialSpecializationDecl>(Record)) {
- const auto &Args = PartialSpec->getTemplateArgs();
+ if (auto *Spec = dyn_cast<ClassTemplateSpecializationDecl>(Record)) {
+ const auto &Args = Spec->getTemplateArgs();
if (Args.size() > 0 && Args[0].getKind() == TemplateArgument::Type)
return Args[0].getAsType();
}
@@ -1784,6 +1783,12 @@ Expr *BuiltinTypeDeclBuilder::getConstantUnsignedIntExpr(unsigned value) {
BuiltinTypeDeclBuilder &
BuiltinTypeDeclBuilder::addSimpleTemplateParams(ArrayRef<StringRef> Names,
ConceptDecl *CD = nullptr) {
+ return addSimpleTemplateParams(Names, {}, CD);
+}
+
+BuiltinTypeDeclBuilder &BuiltinTypeDeclBuilder::addSimpleTemplateParams(
+ ArrayRef<StringRef> Names, ArrayRef<QualType> DefaultValues,
+ ConceptDecl *CD) {
if (Record->isCompleteDefinition()) {
assert(Template && "existing record it not a template");
assert(Template->getTemplateParameters()->size() == Names.size() &&
@@ -1791,9 +1796,14 @@ BuiltinTypeDeclBuilder::addSimpleTemplateParams(ArrayRef<StringRef> Names,
return *this;
}
+ assert((DefaultValues.empty() || DefaultValues.size() == Names.size()) &&
+ "template default argument count mismatch");
+
TemplateParameterListBuilder Builder = TemplateParameterListBuilder(*this);
- for (StringRef Name : Names)
- Builder.addTypeParameter(Name);
+ for (unsigned i = 0; i < Names.size(); ++i) {
+ QualType DefaultTy = DefaultValues.empty() ? QualType() : DefaultValues[i];
+ Builder.addTypeParameter(Names[i], DefaultTy);
+ }
return Builder.finalizeTemplateArgs(CD);
}
diff --git a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h
index c27ff30c6ff73..2b4c36802a512 100644
--- a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h
+++ b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h
@@ -65,6 +65,9 @@ class BuiltinTypeDeclBuilder {
BuiltinTypeDeclBuilder &addSimpleTemplateParams(ArrayRef<StringRef> Names,
ConceptDecl *CD);
+ BuiltinTypeDeclBuilder &
+ addSimpleTemplateParams(ArrayRef<StringRef> Names,
+ ArrayRef<QualType> DefaultValues, ConceptDecl *CD);
CXXRecordDecl *finalizeForwardDeclaration() { return Record; }
BuiltinTypeDeclBuilder &completeDefinition();
diff --git a/clang/lib/Sema/HLSLExternalSemaSource.cpp b/clang/lib/Sema/HLSLExternalSemaSource.cpp
index 788a129ec5390..bb86ef4e063e8 100644
--- a/clang/lib/Sema/HLSLExternalSemaSource.cpp
+++ b/clang/lib/Sema/HLSLExternalSemaSource.cpp
@@ -460,6 +460,7 @@ static ConceptDecl *constructBufferConceptDecl(Sema &S, NamespaceDecl *NSD,
}
void HLSLExternalSemaSource::defineHLSLTypesWithForwardDeclarations() {
+ ASTContext &AST = SemaPtr->getASTContext();
CXXRecordDecl *Decl;
ConceptDecl *TypedBufferConcept = constructBufferConceptDecl(
*SemaPtr, HLSLNamespace, /*isTypedBuffer*/ true);
@@ -612,8 +613,10 @@ void HLSLExternalSemaSource::defineHLSLTypesWithForwardDeclarations() {
setupSamplerType(Decl, *SemaPtr).completeDefinition();
});
+ QualType Float4Ty = AST.getExtVectorType(AST.FloatTy, 4);
Decl = BuiltinTypeDeclBuilder(*SemaPtr, HLSLNamespace, "Texture2D")
- .addSimpleTemplateParams({"element_type"}, TypedBufferConcept)
+ .addSimpleTemplateParams({"element_type"}, {Float4Ty},
+ TypedBufferConcept)
.finalizeForwardDeclaration();
onCompletion(Decl, [this](CXXRecordDecl *Decl) {
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index be84974c70f27..90d21b8229f84 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -553,6 +553,17 @@ ParsedType Sema::getTypeName(const IdentifierInfo &II, SourceLocation NameLoc,
}
return CreateParsedType(T, TLB.getTypeSourceInfo(Context, T));
}
+
+ if (getLangOpts().HLSL) {
+ if (auto *TD = dyn_cast_or_null<TemplateDecl>(
+ getAsTemplateNameDecl(IIDecl, /*AllowFunctionTemplates=*/false,
+ /*AllowDependent=*/false))) {
+ QualType ShorthandTy = HLSL().ActOnTemplateShorthand(TD, NameLoc);
+ if (!ShorthandTy.isNull())
+ return ParsedType::make(ShorthandTy);
+ }
+ }
+
if (ObjCInterfaceDecl *IDecl = dyn_cast<ObjCInterfaceDecl>(IIDecl)) {
(void)DiagnoseUseOfDecl(IDecl, NameLoc);
if (!HasTrailingDot) {
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index 5701b76427d60..d34811db10cbb 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -5548,3 +5548,46 @@ bool SemaHLSL::handleInitialization(VarDecl *VDecl, Expr *&Init) {
Init = C;
return true;
}
+
+QualType SemaHLSL::ActOnTemplateShorthand(TemplateDecl *Template,
+ SourceLocation NameLoc) {
+ if (!Template)
+ return QualType();
+
+ DeclContext *DC = Template->getDeclContext();
+ if (!DC->isNamespace() || !cast<NamespaceDecl>(DC)->getIdentifier() ||
+ cast<NamespaceDecl>(DC)->getName() != "hlsl")
+ return QualType();
+
+ TemplateParameterList *Params = Template->getTemplateParameters();
+ if (!Params || Params->size() != 1)
+ return QualType();
+
+ if (Template->getName() != "Texture2D")
+ return QualType();
+
+ TemplateArgumentListInfo TemplateArgs(NameLoc, NameLoc);
+ for (NamedDecl *P : *Params) {
+ if (auto *TTP = dyn_cast<TemplateTypeParmDecl>(P)) {
+ if (TTP->hasDefaultArgument()) {
+ TemplateArgs.addArgument(TTP->getDefaultArgument());
+ continue;
+ }
+ } else if (auto *NTTP = dyn_cast<NonTypeTemplateParmDecl>(P)) {
+ if (NTTP->hasDefaultArgument()) {
+ TemplateArgs.addArgument(NTTP->getDefaultArgument());
+ continue;
+ }
+ } else if (auto *TTPD = dyn_cast<TemplateTemplateParmDecl>(P)) {
+ if (TTPD->hasDefaultArgument()) {
+ TemplateArgs.addArgument(TTPD->getDefaultArgument());
+ continue;
+ }
+ }
+ return QualType();
+ }
+
+ return SemaRef.CheckTemplateIdType(
+ ElaboratedTypeKeyword::None, TemplateName(Template), NameLoc,
+ TemplateArgs, nullptr, /*ForNestedNameSpecifier=*/false);
+}
diff --git a/clang/test/AST/HLSL/Texture2D-shorthand-AST.hlsl b/clang/test/AST/HLSL/Texture2D-shorthand-AST.hlsl
new file mode 100644
index 0000000000000..7e23474266a01
--- /dev/null
+++ b/clang/test/AST/HLSL/Texture2D-shorthand-AST.hlsl
@@ -0,0 +1,39 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-library -x hlsl -ast-dump -disable-llvm-passes -finclude-default-header -o - %s | FileCheck %s
+
+// CHECK: VarDecl {{.*}} t1 'hlsl::Texture2D<vector<float, 4>>':'hlsl::Texture2D<>'
+Texture2D t1;
+
+// CHECK: VarDecl {{.*}} t1_explicit 'Texture2D<>':'hlsl::Texture2D<>'
+Texture2D<> t1_explicit;
+
+// CHECK: VarDecl {{.*}} t2 'Texture2D<float>':'hlsl::Texture2D<float>'
+Texture2D<float> t2;
+
+// CHECK: VarDecl {{.*}} t3 'Texture2D<float4>':'hlsl::Texture2D<>'
+Texture2D<float4> t3;
+
+// CHECK: TypedefDecl {{.*}} tex_alias 'hlsl::Texture2D<vector<float, 4>>':'hlsl::Texture2D<>'
+typedef Texture2D tex_alias;
+
+struct S {
+ // CHECK: FieldDecl {{.*}} tex 'hlsl::Texture2D<vector<float, 4>>':'hlsl::Texture2D<>'
+ Texture2D tex;
+};
+
+// CHECK: FunctionDecl {{.*}} foo 'hlsl::Texture2D<vector<float, 4>> (hlsl::Texture2D<vector<float, 4>>)'
+// CHECK: ParmVarDecl {{.*}} p 'hlsl::Texture2D<vector<float, 4>>':'hlsl::Texture2D<>'
+Texture2D foo(Texture2D p) {
+ // CHECK: VarDecl {{.*}} local 'hlsl::Texture2D<vector<float, 4>>':'hlsl::Texture2D<>'
+ Texture2D local;
+ return local;
+}
+
+template<typename T>
+void template_foo(T p) {
+ // CHECK: VarDecl {{.*}} local 'hlsl::Texture2D<vector<float, 4>>':'hlsl::Texture2D<>'
+ Texture2D local;
+}
+
+void main() {
+ template_foo(1);
+}
diff --git a/clang/test/CodeGenHLSL/resources/Texture2D-default-explicit-binding.hlsl b/clang/test/CodeGenHLSL/resources/Texture2D-default-explicit-binding.hlsl
new file mode 100644
index 0000000000000..17ffc7a85aa3a
--- /dev/null
+++ b/clang/test/CodeGenHLSL/resources/Texture2D-default-explicit-binding.hlsl
@@ -0,0 +1,24 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-library -x hlsl -finclude-default-header -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s
+// RUN: %clang_cc1 -triple spirv-vulkan-library -x hlsl -finclude-default-header -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s --check-prefix=SPIRV
+
+SamplerState g_s : register(s0);
+Texture2D<> default_template : register(t1, space2);
+Texture2D implicit_template : register(t0, space1);
+
+// CHECK: @{{.*}}default_template = internal global %"class.hlsl::Texture2D" poison, align {{[0-9]+}}
+// CHECK: @{{.*}}implicit_template = internal global %"class.hlsl::Texture2D" poison, align {{[0-9]+}}
+// SPIRV: @{{.*}}default_template = internal global %"class.hlsl::Texture2D" poison, align {{[0-9]+}}
+// SPIRV: @{{.*}}implicit_template = internal global %"class.hlsl::Texture2D" poison, align {{[0-9]+}}
+
+[shader("pixel")]
+float4 main(float2 uv : TEXCOORD) : SV_Target {
+ return implicit_template.Sample(g_s, uv) + default_template.Sample(g_s, uv);
+}
+
+// CHECK: call void @{{.*}}__createFromBinding{{.*}}(ptr {{.*}}@{{.*}}default_template, i32 noundef 1, i32 noundef 2, i32 noundef 1, i32 noundef 0, ptr noundef @{{.*}})
+// CHECK: call void @{{.*}}__createFromBinding{{.*}}(ptr {{.*}}@{{.*}}implicit_template, i32 noundef 0, i32 noundef 1, i32 noundef 1, i32 noundef 0, ptr noundef @{{.*}})
+// SPIRV: call void @{{.*}}__createFromBinding{{.*}}(ptr {{.*}}@{{.*}}default_template, i32 noundef 1, i32 noundef 2, i32 noundef 1, i32 noundef 0, ptr noundef @{{.*}})
+// SPIRV: call void @{{.*}}__createFromBinding{{.*}}(ptr {{.*}}@{{.*}}implicit_template, i32 noundef 0, i32 noundef 1, i32 noundef 1, i32 noundef 0, ptr noundef @{{.*}})
+// CHECK: define void @main()
+// SPIRV: define void @main()
+
diff --git a/clang/test/CodeGenHLSL/resources/Texture2D-default.hlsl b/clang/test/CodeGenHLSL/resources/Texture2D-default.hlsl
new file mode 100644
index 0000000000000..5b5bb737a7958
--- /dev/null
+++ b/clang/test/CodeGenHLSL/resources/Texture2D-default.hlsl
@@ -0,0 +1,16 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-library -x hlsl -std=hlsl202x -emit-llvm -disable-llvm-passes -finclude-default-header -o - %s | FileCheck %s
+
+// CHECK: %"class.hlsl::Texture2D" = type { target("dx.Texture", <4 x float>, 0, 0, 0, 2) }
+// CHECK: %"class.hlsl::Texture2D.0" = type { target("dx.Texture", float, 0, 0, 0, 2) }
+
+// CHECK: @{{.*}}t1 = internal global %"class.hlsl::Texture2D" poison, align 4
+Texture2D<> t1;
+
+// CHECK: @{{.*}}t2 = internal global %"class.hlsl::Texture2D.0" poison, align 4
+Texture2D<float> t2;
+
+// CHECK: @{{.*}}t3 = internal global %"class.hlsl::Texture2D" poison, align 4
+Texture2D t3;
+
+void main() {
+}
diff --git a/clang/test/CodeGenHLSL/resources/Texture2D-shorthand-contexts.hlsl b/clang/test/CodeGenHLSL/resources/Texture2D-shorthand-contexts.hlsl
new file mode 100644
index 0000000000000..71ce46232d088
--- /dev/null
+++ b/clang/test/CodeGenHLSL/resources/Texture2D-shorthand-contexts.hlsl
@@ -0,0 +1,31 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-library -x hlsl -finclude-default-header -emit-llvm -disable-llvm-passes -o - %s | llvm-cxxfilt | FileCheck %s
+
+// CHECK: %"class.hlsl::Texture2D" = type { target("dx.Texture", <4 x float>, 0, 0, 0, 2) }
+
+SamplerState g_s : register(s0);
+
+struct S {
+ Texture2D tex;
+};
+
+// CHECK: define {{.*}}void @use_struct(S)(ptr noundef {{.*}}%s)
+void use_struct(S s) {
+ // CHECK: call {{.*}} <4 x float> @hlsl::Texture2D<float vector[4]>::Sample(hlsl::SamplerState, float vector[2])
+ float4 val = s.tex.Sample(g_s, float2(0.5, 0.5));
+}
+
+// CHECK: define {{.*}}void @use_param(hlsl::Texture2D<float vector[4]>)(ptr noundef {{.*}}%p)
+void use_param(Texture2D p) {
+ // CHECK: call {{.*}} <4 x float> @hlsl::Texture2D<float vector[4]>::Sample(hlsl::SamplerState, float vector[2])
+ float4 val = p.Sample(g_s, float2(0.5, 0.5));
+}
+
+[shader("pixel")]
+float4 main() : SV_Target {
+ // CHECK: %local = alloca %"class.hlsl::Texture2D"
+ Texture2D local;
+ // CHECK: call {{.*}} <4 x float> @hlsl::Texture2D<float vector[4]>::Sample(hlsl::SamplerState, float vector[2])
+ return local.Sample(g_s, float2(0.5, 0.5));
+}
+
+// CHECK: declare <4 x float> @llvm.dx.resource.sample.v4f32
>From fa149e2e114d086be32fe42e96af4303ef77c8f1 Mon Sep 17 00:00:00 2001
From: Steven Perron <stevenperron at google.com>
Date: Fri, 6 Mar 2026 09:40:12 -0500
Subject: [PATCH 2/3] [HLSL] Improve Texture2D default template and add
resource negative tests
- Replace name check for Texture2D with isImplicit() in SemaHLSL to avoid conflicts with user-defined types.
- Add LLVM IR type assertions for class.hlsl::Texture2D in CodeGen tests.
- Add negative tests to verify that resource types (Buffer, RWBuffer, StructuredBuffer, RWStructuredBuffer) require template arguments when used as function parameters, return types, or struct members.
- Rename template parameter variable in HLSLBuiltinTypeDeclBuilder for clarity.
Part of #175630.
---
clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp | 11 ++---
clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h | 2 +-
clang/lib/Sema/SemaHLSL.cpp | 2 +-
.../Texture2D-default-explicit-binding.hlsl | 3 ++
clang/test/SemaHLSL/BuiltIns/Buffers.hlsl | 14 ++++++
clang/test/SemaHLSL/BuiltIns/RWBuffers.hlsl | 14 ++++++
.../BuiltIns/RWStructuredBuffers.hlsl | 44 +++++++++++++++++++
.../SemaHLSL/BuiltIns/StructuredBuffers.hlsl | 14 ++++++
8 files changed, 97 insertions(+), 7 deletions(-)
create mode 100644 clang/test/SemaHLSL/BuiltIns/RWStructuredBuffers.hlsl
diff --git a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp
index 2e535c02e5752..f9f57824bb48c 100644
--- a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp
+++ b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.cpp
@@ -1786,9 +1786,10 @@ BuiltinTypeDeclBuilder::addSimpleTemplateParams(ArrayRef<StringRef> Names,
return addSimpleTemplateParams(Names, {}, CD);
}
-BuiltinTypeDeclBuilder &BuiltinTypeDeclBuilder::addSimpleTemplateParams(
- ArrayRef<StringRef> Names, ArrayRef<QualType> DefaultValues,
- ConceptDecl *CD) {
+BuiltinTypeDeclBuilder &
+BuiltinTypeDeclBuilder::addSimpleTemplateParams(ArrayRef<StringRef> Names,
+ ArrayRef<QualType> DefaultTypes,
+ ConceptDecl *CD) {
if (Record->isCompleteDefinition()) {
assert(Template && "existing record it not a template");
assert(Template->getTemplateParameters()->size() == Names.size() &&
@@ -1796,12 +1797,12 @@ BuiltinTypeDeclBuilder &BuiltinTypeDeclBuilder::addSimpleTemplateParams(
return *this;
}
- assert((DefaultValues.empty() || DefaultValues.size() == Names.size()) &&
+ assert((DefaultTypes.empty() || DefaultTypes.size() == Names.size()) &&
"template default argument count mismatch");
TemplateParameterListBuilder Builder = TemplateParameterListBuilder(*this);
for (unsigned i = 0; i < Names.size(); ++i) {
- QualType DefaultTy = DefaultValues.empty() ? QualType() : DefaultValues[i];
+ QualType DefaultTy = DefaultTypes.empty() ? QualType() : DefaultTypes[i];
Builder.addTypeParameter(Names[i], DefaultTy);
}
return Builder.finalizeTemplateArgs(CD);
diff --git a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h
index 2b4c36802a512..6e68c1f33d723 100644
--- a/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h
+++ b/clang/lib/Sema/HLSLBuiltinTypeDeclBuilder.h
@@ -67,7 +67,7 @@ class BuiltinTypeDeclBuilder {
ConceptDecl *CD);
BuiltinTypeDeclBuilder &
addSimpleTemplateParams(ArrayRef<StringRef> Names,
- ArrayRef<QualType> DefaultValues, ConceptDecl *CD);
+ ArrayRef<QualType> DefaultTypes, ConceptDecl *CD);
CXXRecordDecl *finalizeForwardDeclaration() { return Record; }
BuiltinTypeDeclBuilder &completeDefinition();
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index d34811db10cbb..6f1b8c52b36ac 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -5563,7 +5563,7 @@ QualType SemaHLSL::ActOnTemplateShorthand(TemplateDecl *Template,
if (!Params || Params->size() != 1)
return QualType();
- if (Template->getName() != "Texture2D")
+ if (!Template->isImplicit())
return QualType();
TemplateArgumentListInfo TemplateArgs(NameLoc, NameLoc);
diff --git a/clang/test/CodeGenHLSL/resources/Texture2D-default-explicit-binding.hlsl b/clang/test/CodeGenHLSL/resources/Texture2D-default-explicit-binding.hlsl
index 17ffc7a85aa3a..06b4cb7ec9900 100644
--- a/clang/test/CodeGenHLSL/resources/Texture2D-default-explicit-binding.hlsl
+++ b/clang/test/CodeGenHLSL/resources/Texture2D-default-explicit-binding.hlsl
@@ -5,6 +5,9 @@ SamplerState g_s : register(s0);
Texture2D<> default_template : register(t1, space2);
Texture2D implicit_template : register(t0, space1);
+// CHECK: %"class.hlsl::Texture2D" = type { target("dx.Texture", <4 x float>, 0, 0, 0, 2) }
+// SPIRV: %"class.hlsl::Texture2D" = type { target("spirv.Image", float, 1, 2, 0, 0, 1, 0) }
+
// CHECK: @{{.*}}default_template = internal global %"class.hlsl::Texture2D" poison, align {{[0-9]+}}
// CHECK: @{{.*}}implicit_template = internal global %"class.hlsl::Texture2D" poison, align {{[0-9]+}}
// SPIRV: @{{.*}}default_template = internal global %"class.hlsl::Texture2D" poison, align {{[0-9]+}}
diff --git a/clang/test/SemaHLSL/BuiltIns/Buffers.hlsl b/clang/test/SemaHLSL/BuiltIns/Buffers.hlsl
index 999372c95554e..8debd38801bd4 100644
--- a/clang/test/SemaHLSL/BuiltIns/Buffers.hlsl
+++ b/clang/test/SemaHLSL/BuiltIns/Buffers.hlsl
@@ -116,3 +116,17 @@ void main() {
// expected-note@* {{function 'operator[]' which returns const-qualified type 'vector<float const hlsl_device &, 3>' declared here}}
Buff[0] = 0.0;
}
+
+// expected-error at +2 {{class template 'Buffer' requires template arguments}}
+// expected-note@*:* {{template declaration from hidden source: template <typename element_type> requires __is_typed_resource_element_compatible<element_type> class Buffer {}}}
+void f1(Buffer B) {}
+
+// expected-error at +2 {{class template 'Buffer' requires template arguments}}
+// expected-note@*:* {{template declaration from hidden source: template <typename element_type> requires __is_typed_resource_element_compatible<element_type> class Buffer {}}}
+Buffer f2();
+
+struct S {
+ // expected-error at +2 {{class template 'Buffer' requires template arguments}}
+ // expected-note@*:* {{template declaration from hidden source: template <typename element_type> requires __is_typed_resource_element_compatible<element_type> class Buffer {}}}
+ Buffer B;
+};
diff --git a/clang/test/SemaHLSL/BuiltIns/RWBuffers.hlsl b/clang/test/SemaHLSL/BuiltIns/RWBuffers.hlsl
index b33f2af8a1117..a767743a0eccc 100644
--- a/clang/test/SemaHLSL/BuiltIns/RWBuffers.hlsl
+++ b/clang/test/SemaHLSL/BuiltIns/RWBuffers.hlsl
@@ -112,3 +112,17 @@ void main() {
(void)Buff.__handle; // expected-error {{'__handle' is a private member of 'hlsl::RWBuffer<vector<float, 3>>'}}
// expected-note@* {{implicitly declared private here}}
}
+
+// expected-error at +2 {{class template 'RWBuffer' requires template arguments}}
+// expected-note@*:* {{template declaration from hidden source: template <typename element_type> requires __is_typed_resource_element_compatible<element_type> class RWBuffer {}}}
+void f1(RWBuffer B) {}
+
+// expected-error at +2 {{class template 'RWBuffer' requires template arguments}}
+// expected-note@*:* {{template declaration from hidden source: template <typename element_type> requires __is_typed_resource_element_compatible<element_type> class RWBuffer {}}}
+RWBuffer f2();
+
+struct S {
+ // expected-error at +2 {{class template 'RWBuffer' requires template arguments}}
+ // expected-note@*:* {{template declaration from hidden source: template <typename element_type> requires __is_typed_resource_element_compatible<element_type> class RWBuffer {}}}
+ RWBuffer B;
+};
diff --git a/clang/test/SemaHLSL/BuiltIns/RWStructuredBuffers.hlsl b/clang/test/SemaHLSL/BuiltIns/RWStructuredBuffers.hlsl
new file mode 100644
index 0000000000000..30c04937f28e3
--- /dev/null
+++ b/clang/test/SemaHLSL/BuiltIns/RWStructuredBuffers.hlsl
@@ -0,0 +1,44 @@
+// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-compute -x hlsl -fsyntax-only -verify %s
+
+typedef vector<float, 3> float3;
+
+RWStructuredBuffer<float3> Buff;
+
+// expected-error at +2 {{class template 'RWStructuredBuffer' requires template arguments}}
+// expected-note@*:* {{template declaration from hidden source: template <typename element_type> requires __is_structured_resource_element_compatible<element_type> class RWStructuredBuffer {}}}
+RWStructuredBuffer BufferErr1;
+
+// expected-error at +2 {{too few template arguments for class template 'RWStructuredBuffer'}}
+// expected-note@*:* {{template declaration from hidden source: template <typename element_type> requires __is_structured_resource_element_compatible<element_type> class RWStructuredBuffer {}}}
+RWStructuredBuffer<> BufferErr2;
+
+// test elements of 0 size
+// expected-error at +3{{constraints not satisfied for class template 'RWStructuredBuffer' [with element_type = int[0]]}}
+// expected-note@*:*{{because 'int[0]' does not satisfy '__is_structured_resource_element_compatible'}}
+// expected-note@*:*{{because 'sizeof(int[0]) >= 1UL' (0 >= 1) evaluated to false}}
+RWStructuredBuffer<int[0]> BufferErr3;
+
+// In C++, empty structs do have a size of 1. So should HLSL.
+// The concept will accept empty structs as element types, despite it being unintuitive.
+struct Empty {};
+RWStructuredBuffer<Empty> BufferErr4;
+
+[numthreads(1,1,1)]
+void main() {
+ (void)Buff.__handle; // expected-error {{'__handle' is a private member of 'hlsl::RWStructuredBuffer<vector<float, 3>>'}}
+ // expected-note@* {{implicitly declared private here}}
+}
+
+// expected-error at +2 {{class template 'RWStructuredBuffer' requires template arguments}}
+// expected-note@*:* {{template declaration from hidden source: template <typename element_type> requires __is_structured_resource_element_compatible<element_type> class RWStructuredBuffer {}}}
+void f1(RWStructuredBuffer B) {}
+
+// expected-error at +2 {{class template 'RWStructuredBuffer' requires template arguments}}
+// expected-note@*:* {{template declaration from hidden source: template <typename element_type> requires __is_structured_resource_element_compatible<element_type> class RWStructuredBuffer {}}}
+RWStructuredBuffer f2();
+
+struct S {
+// expected-error at +2 {{class template 'RWStructuredBuffer' requires template arguments}}
+// expected-note@*:* {{template declaration from hidden source: template <typename element_type> requires __is_structured_resource_element_compatible<element_type> class RWStructuredBuffer {}}}
+ RWStructuredBuffer B;
+};
diff --git a/clang/test/SemaHLSL/BuiltIns/StructuredBuffers.hlsl b/clang/test/SemaHLSL/BuiltIns/StructuredBuffers.hlsl
index e5b1125b873e1..ba63fc25819e5 100644
--- a/clang/test/SemaHLSL/BuiltIns/StructuredBuffers.hlsl
+++ b/clang/test/SemaHLSL/BuiltIns/StructuredBuffers.hlsl
@@ -33,3 +33,17 @@ void main() {
// expected-note@* {{function 'operator[]' which returns const-qualified type 'vector<float const hlsl_device &, 3>' declared here}}
Buff[0] = 0.0;
}
+
+// expected-error at +2 {{class template 'StructuredBuffer' requires template arguments}}
+// expected-note@*:* {{template declaration from hidden source: template <typename element_type> requires __is_structured_resource_element_compatible<element_type> class StructuredBuffer {}}}
+void f1(StructuredBuffer B) {}
+
+// expected-error at +2 {{class template 'StructuredBuffer' requires template arguments}}
+// expected-note@*:* {{template declaration from hidden source: template <typename element_type> requires __is_structured_resource_element_compatible<element_type> class StructuredBuffer {}}}
+StructuredBuffer f2();
+
+struct S {
+// expected-error at +2 {{class template 'StructuredBuffer' requires template arguments}}
+// expected-note@*:* {{template declaration from hidden source: template <typename element_type> requires __is_structured_resource_element_compatible<element_type> class StructuredBuffer {}}}
+ StructuredBuffer B;
+};
>From f5eaedddbb8928e0d3249d5e86b008fe701eaaa1 Mon Sep 17 00:00:00 2001
From: Steven Perron <stevenperron at google.com>
Date: Fri, 6 Mar 2026 10:00:48 -0500
Subject: [PATCH 3/3] [HLSL] Explain manual default argument extraction for
template shorthand
Add a comment explaining that manually extracting default arguments in
ActOnTemplateShorthand is necessary to provide better diagnostic error
messages. It ensures that resource types without a default argument
(like Buffer) trigger a 'requires template arguments' error instead of
a 'too few template arguments' error.
Part of #175630.
---
clang/lib/Sema/SemaHLSL.cpp | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index 6f1b8c52b36ac..87eaa754ed0d4 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -5566,6 +5566,11 @@ QualType SemaHLSL::ActOnTemplateShorthand(TemplateDecl *Template,
if (!Template->isImplicit())
return QualType();
+ // We manually extract default arguments here instead of letting
+ // CheckTemplateIdType handle it. This ensures that for resource types that
+ // lack a default argument (like Buffer), we return a null QualType, which
+ // triggers the "requires template arguments" error rather than a less
+ // descriptive "too few template arguments" error.
TemplateArgumentListInfo TemplateArgs(NameLoc, NameLoc);
for (NamedDecl *P : *Params) {
if (auto *TTP = dyn_cast<TemplateTypeParmDecl>(P)) {
More information about the cfe-commits
mailing list