https://github.com/philnik777 created https://github.com/llvm/llvm-project/pull/211231
This attribute ensures that the provided type is complete when any of the member functions of a class are instantiated. This allows easily enforcing this invariant across an entire class (e.g. `std::vector`) without having to `static_assert(sizeof(T) >= 0)` in every single member function. It also improves error messages, since the call site is diagnosed instead of something potentially deep within the function instantiation. >From 696d3c098bb9d2d515eebbbe46a9294149f3e599 Mon Sep 17 00:00:00 2001 From: Nikolas Klauser <[email protected]> Date: Wed, 22 Jul 2026 12:58:27 +0200 Subject: [PATCH] [Clang] Add [[clang::complete_on_member_instantiation]] --- clang/include/clang/Basic/Attr.td | 11 ++++- .../clang/Basic/DiagnosticSemaKinds.td | 3 ++ clang/lib/Sema/SemaDeclAttr.cpp | 15 ++++++ .../lib/Sema/SemaTemplateInstantiateDecl.cpp | 8 +++ .../attr-complete-on-member-instantiation.cpp | 49 +++++++++++++++++++ 5 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 clang/test/SemaCXX/attr-complete-on-member-instantiation.cpp diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index 1a5bd2301dfc8..fdead2eba2492 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -5316,7 +5316,7 @@ def HLSLVkLocation : HLSLAnnotationAttr { } // `row_major` / `column_major` are HLSL keywords that select the in-memory -// layout of a matrix-typed declaration. +// layout of a matrix-typed declaration. def HLSLRowMajor : TypeAttr { let Spellings = [CustomKeyword<"row_major">]; let LangOpts = [HLSL]; @@ -5503,3 +5503,12 @@ def Personality : InheritableAttr { let Subjects = SubjectList<[Function]>; let Documentation = [PersonalityDocs]; } + +def CompleteOnMemberInst : InheritableAttr { + let Spellings = [Clang<"complete_on_member_instantiation">]; + let Args = [TypeArgument<"CompleteType">]; + let Subjects = SubjectList<[CXXRecord]>; + let Documentation = [Undocumented]; + let InheritEvenIfAlreadyPresent = 1; + let TemplateDependent = 1; +} diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 3ff7e30d9f1f7..5e2b82545f907 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -2182,6 +2182,9 @@ def ext_out_of_line_qualified_id_type_names_constructor : ExtWarn< "%select{'typename'|'template'}2 keyword">, SFINAEFailure, InGroup<DiagGroup<"injected-class-name">>; +def err_incomplete_type_on_member_inst : Error< + "%0 has to be complete when calling a member function">; + // C++ class members def err_storageclass_invalid_for_member : Error< "storage class specified for a member declaration">; diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index 1b272b5416860..59bc09e471075 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -2071,6 +2071,18 @@ static void handleCommonAttr(Sema &S, Decl *D, const ParsedAttr &AL) { D->addAttr(::new (S.Context) CommonAttr(S.Context, AL)); } +static void handleCompleteOnMemberInstAttr(Sema &S, Decl *D, const ParsedAttr &AL) { + if (!AL.hasParsedType()) { + S.Diag(AL.getLoc(), diag::err_attribute_wrong_number_arguments) << AL << 1; + return; + } + + TypeSourceInfo *ParmTSI = nullptr; + S.GetTypeFromParser(AL.getTypeArg(), &ParmTSI); + assert(ParmTSI && "no type source info for attribute argument"); + D->addAttr(CompleteOnMemberInstAttr::Create(S.Context, ParmTSI, AL)); +} + static void handleNakedAttr(Sema &S, Decl *D, const ParsedAttr &AL) { if (AL.isDeclspecAttribute()) { const auto &Triple = S.getASTContext().getTargetInfo().getTriple(); @@ -7787,6 +7799,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL, case ParsedAttr::AT_Common: handleCommonAttr(S, D, AL); break; + case ParsedAttr::AT_CompleteOnMemberInst: + handleCompleteOnMemberInstAttr(S, D, AL); + break; case ParsedAttr::AT_CUDAConstant: handleConstantAttr(S, D, AL); break; diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp index c2e90624b8a6e..0776144f8826e 100644 --- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -5766,6 +5766,14 @@ void Sema::InstantiateFunctionDefinition(SourceLocation PointOfInstantiation, } } + if (auto *RD = dyn_cast<CXXRecordDecl>(Function->getDeclContext())) { + for (auto *COMI : RD->specific_attrs<CompleteOnMemberInstAttr>()) { + if (RequireCompleteType(PointOfInstantiation, COMI->getCompleteType(), + diag::err_incomplete_type_on_member_inst)) + return; + } + } + NonSFINAEContext _(*this); InstantiatingTemplate Inst(*this, PointOfInstantiation, Function); if (Inst.isInvalid()) diff --git a/clang/test/SemaCXX/attr-complete-on-member-instantiation.cpp b/clang/test/SemaCXX/attr-complete-on-member-instantiation.cpp new file mode 100644 index 0000000000000..3b4de2963bb27 --- /dev/null +++ b/clang/test/SemaCXX/attr-complete-on-member-instantiation.cpp @@ -0,0 +1,49 @@ +// RUN: %clang_cc1 %s -verify -fsyntax-only + +template <class T> +struct [[clang::complete_on_member_instantiation]] S1 {}; // expected-error {{'clang::complete_on_member_instantiation' attribute takes one argument}} + +template <class T> +struct [[clang::complete_on_member_instantiation(1)]] S2 {}; // expected-error {{expected a type}} + +template <class T> +struct [[clang::complete_on_member_instantiation(T)]] RequiresComplete { + void func() {} +}; + +struct Complete {}; +struct Incomplete; // expected-note 7 {{forward declaration of 'Incomplete'}} + +void func(RequiresComplete<Incomplete>& incomplete, RequiresComplete<Complete>& complete) { + complete.func(); + incomplete.func(); // expected-error {{'Incomplete' has to be complete when calling a member function}} +} + +template <class T, class U> +struct [[clang::complete_on_member_instantiation(T), clang::complete_on_member_instantiation(U)]] RequiresComplete2 { + void func() {} +}; + +void func2(RequiresComplete2<Incomplete, Incomplete> both_incomplete, + RequiresComplete2<Incomplete, Complete> first_incomplete, + RequiresComplete2<Complete, Incomplete> second_incomplete) { + both_incomplete.func(); // expected-error {{'Incomplete' has to be complete when calling a member function}} + first_incomplete.func(); // expected-error {{'Incomplete' has to be complete when calling a member function}} + second_incomplete.func(); // expected-error {{'Incomplete' has to be complete when calling a member function}} +} + +template <class T, class U> +struct [[clang::complete_on_member_instantiation(T), clang::complete_on_member_instantiation(U)]] RequiresComplete3; + +template <class T, class U> +struct RequiresComplete3 { + void func() {} +}; + +void func2(RequiresComplete3<Incomplete, Incomplete> both_incomplete, + RequiresComplete3<Incomplete, Complete> first_incomplete, + RequiresComplete3<Complete, Incomplete> second_incomplete) { + both_incomplete.func(); // expected-error {{'Incomplete' has to be complete when calling a member function}} + first_incomplete.func(); // expected-error {{'Incomplete' has to be complete when calling a member function}} + second_incomplete.func(); // expected-error {{'Incomplete' has to be complete when calling a member function}} +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
