[clang][reflection] Complete reflected specializations with implicit-instantiation semantics - #297
Merged
zebullax merged 1 commit intoJun 30, 2026
Conversation
…instantiation semantics SemaMetaActions::EnsureInstantiated -- the single completion funnel for every type-completing metafunction (members_of, bases_of, size_of, is_complete_type, ...) -- completed class template specializations with TSK_ExplicitInstantiationDefinition plus InstantiateClassTemplateSpecializationMembers, eagerly instantiating every member DEFINITION. A specialization whose never-odr-used member bodies are ill-formed -- the "specialized storage base" idiom, valid C++ as long as those members are never odr-used; tl::expected<void, E> is the field shape (nine hard errors out of its implementation details) -- was wrong-rejected, but ONLY when reflection reached the type before ordinary use did: a preceding `Exp<void> ok_instance;` made the identical members_of loop compile clean (order-dependent enumeration). The eager kind also marked the specialization as if the user had written an explicit instantiation definition, forcing weak-ODR emission of every member in the TU and colliding with genuine explicit instantiations. Fix: mirror Sema::RequireCompleteTypeImpl -- TSK_ImplicitInstantiation with the specialization's strict-pack-match flag, member definitions left to lazy odr-use-driven instantiation, plus the member-class-of-a-template branch (InstantiateClass on getInstantiatedFromMemberClass) that the eager member sweep used to cover as a side effect. Body-needing consumers (extract, reflect_invoke, deduced-return substitute) re-enter EnsureInstantiated with the specific FunctionDecl/VarDecl, whose branches are untouched. Test: members-of-lazily-ill-formed-bodies.pass.cpp -- five distinct templates so each metafunction is the FIRST instantiation trigger, an order-independence control, and a runtime check that odr-used bodies still instantiate lazily. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
19 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #296.
Problem
SemaMetaActions::EnsureInstantiated— the single completion funnel for every type-completing metafunction (members_of,bases_of,size_of,is_complete_type, ...) — completed class template specializations withTSK_ExplicitInstantiationDefinitionplusInstantiateClassTemplateSpecializationMembers, eagerly instantiating every member definition. A specialization whose never-odr-used member bodies are ill-formed (the "specialized storage base" idiom — valid C++;tl::expected<void, E>is the field shape) was wrong-rejected, order-dependently: a preceding ordinary use completed the class lazily first and the identicalmembers_ofloop then compiled clean (see #296 for the reproducer +-DPREINSTANTIATEcontrol). The eager kind also marked the specialization as a user-written explicit instantiation definition — weak-ODR emission of every member in the TU, and collisions with genuinetemplate struct ...;declarations.Fix
clang/lib/Sema/SemaReflect.cpp,EnsureInstantiated: mirrorSema::RequireCompleteTypeImpl—InstantiateClassTemplateSpecialization(..., TSK_ImplicitInstantiation, /*Complain=*/false, CTSD->hasStrictPackMatch());InstantiateClassTemplateSpecializationMemberssweep entirely (a reflection query needs completeness — layout, bases, declared members — never member bodies);InstantiateClassongetInstantiatedFromMemberClass,TSK_ImplicitInstantiation) that the eager member sweep used to cover as a side effect, somembers_of(^^Outer<T>::Inner)keeps working when reflection completesInneron demand.Body-needing consumers (
extract,reflect_invoke,substituteof deduced-return functions) re-enterEnsureInstantiatedwith the specificFunctionDecl/VarDecl; those branches are untouched, so constant-evaluating a member of an implicitly-instantiated specialization still instantiates its body on demand.Test
libcxx/test/std/experimental/reflection/members-of-lazily-ill-formed-bodies.pass.cpp(new): the specialized-storage-base idiom with five distinct templates so each metafunction (members_of,nonstatic_data_members_of,bases_of,is_complete_type,size_of) is the first instantiation trigger for its specialization; an order-independence control (ordinary use first → identical enumeration); and a runtime check that odr-used bodies still instantiate lazily.Validation
837da39eb88c(currentp2996tip; applies cleanly, builds standalone).members_ofinstantiates member function definitions when it triggers the class template specialization's instantiation — valid types with lazily-ill-formed member bodies are wrong-rejected (order-dependent) #296 reproducer errors (no member named 'm_val' in 'Exp<void>'from inside themembers_ofiteration) and is order-dependent; the new test wrong-rejects.clang/test/Reflection: 16/16 with the fix on this machine — identical to base. Body-needing suite spot-checks green:reflect-invoke.pass.cpp,to-and-from-values.pass.cpp,consteval-reentrant-instantiation.pass.cpp,members-and-subobjects.pass.cpp.tl::expected<void, std::string>in the bind set passes its full differential suite (previously: nine hard errors from tl's implementation details on bare enumeration).🤖 Generated with Claude Code