Skip to content

Commit 18168a9

Browse files
Cfretz244claude
andcommitted
[clang][reflection] Complete reflected specializations with implicit-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>
1 parent 837da39 commit 18168a9

2 files changed

Lines changed: 126 additions & 5 deletions

File tree

clang/lib/Sema/SemaReflect.cpp

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -287,13 +287,37 @@ class MetaActionsImpl : public MetaActions {
287287
CTSD->getTemplateArgs().asArray()))
288288
return true;
289289

290+
// Complete the specialization with ordinary implicit-instantiation
291+
// semantics, mirroring Sema::RequireCompleteTypeImpl: member
292+
// DECLARATIONS are instantiated, member DEFINITIONS remain subject to
293+
// lazy, odr-use-driven instantiation. Completing with
294+
// TSK_ExplicitInstantiationDefinition and instantiating every member
295+
// definition wrong-rejected specializations whose never-odr-used member
296+
// bodies are ill-formed (the "specialized storage base" idiom, e.g.
297+
// tl::expected<void, E>), and made the result depend on whether earlier
298+
// code happened to have instantiated the class already.
290299
if (S.InstantiateClassTemplateSpecialization(
291-
Range.getBegin(), CTSD, TSK_ExplicitInstantiationDefinition, false,
292-
false))
300+
Range.getBegin(), CTSD, TSK_ImplicitInstantiation,
301+
/*Complain=*/false, CTSD->hasStrictPackMatch()))
302+
return false;
303+
} else if (auto *RD = dyn_cast<CXXRecordDecl>(D);
304+
RD && !RD->isCompleteDefinition() && !RD->isBeingDefined() &&
305+
!RD->isDependentContext() &&
306+
RD->getInstantiatedFromMemberClass()) {
307+
// A member class of an instantiated class template: complete it the way
308+
// Sema::RequireCompleteTypeImpl would. (The eager path above used to
309+
// define nested classes as a side effect of instantiating every member
310+
// of the enclosing specialization; completing on demand keeps them
311+
// reachable for reflection queries.)
312+
MemberSpecializationInfo *MSI = RD->getMemberSpecializationInfo();
313+
assert(MSI && "missing member specialization information");
314+
if (MSI->getTemplateSpecializationKind() != TSK_ExplicitSpecialization &&
315+
S.InstantiateClass(Range.getBegin(), RD,
316+
RD->getInstantiatedFromMemberClass(),
317+
S.getTemplateInstantiationArgs(RD),
318+
TSK_ImplicitInstantiation,
319+
/*Complain=*/false))
293320
return false;
294-
295-
S.InstantiateClassTemplateSpecializationMembers(
296-
Range.getBegin(), CTSD, TSK_ExplicitInstantiationDefinition);
297321
} else if (auto *VTSD = dyn_cast<VarTemplateSpecializationDecl>(D);
298322
VTSD && !VTSD->isCompleteDefinition()) {
299323
if (!validateConstraints(VTSD->getSpecializedTemplate(),
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Copyright 2024 Bloomberg Finance L.P.
4+
//
5+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
6+
// See https://llvm.org/LICENSE.txt for license information.
7+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
8+
//
9+
//===----------------------------------------------------------------------===//
10+
11+
// UNSUPPORTED: c++03 || c++11 || c++14 || c++17 || c++20
12+
// ADDITIONAL_COMPILE_FLAGS: -freflection-latest
13+
14+
// <experimental/reflection>
15+
//
16+
// [reflection]
17+
//
18+
// Regression test: a type-completing metafunction (members_of & co.), when it
19+
// is the FIRST thing to instantiate a class template specialization, must
20+
// complete it with ordinary implicit-instantiation semantics -- member
21+
// DECLARATIONS instantiated, member DEFINITIONS not. EnsureInstantiated used
22+
// TSK_ExplicitInstantiationDefinition plus
23+
// InstantiateClassTemplateSpecializationMembers, eagerly instantiating every
24+
// member body; a specialization whose never-odr-used member bodies are
25+
// ill-formed (the "specialized storage base" idiom, e.g. tl::expected<void, E>
26+
// -- valid C++ as long as those members are never odr-used) was
27+
// wrong-rejected, but ONLY when reflection got to the type before ordinary
28+
// use did (order-dependent enumeration).
29+
30+
#include <meta>
31+
32+
#include <cassert>
33+
#include <string_view>
34+
35+
namespace meta = std::meta;
36+
constexpr auto ctx = meta::access_context::unchecked();
37+
38+
template <class T> struct storage { T m_val; };
39+
template <> struct storage<void> { char m_dummy; };
40+
41+
// Each probe below must be the FIRST instantiation trigger for its
42+
// specialization, so each metafunction gets a distinct template.
43+
template <class T> struct Exp1 : private storage<T> {
44+
T *valptr() { return &this->m_val; } // body ill-formed for T = void
45+
bool has_value() const { return true; }
46+
};
47+
template <class T> struct Exp2 : private storage<T> {
48+
T *valptr() { return &this->m_val; }
49+
bool has_value() const { return true; }
50+
};
51+
template <class T> struct Exp3 : private storage<T> {
52+
T *valptr() { return &this->m_val; }
53+
bool has_value() const { return true; }
54+
};
55+
template <class T> struct Exp4 : private storage<T> {
56+
T *valptr() { return &this->m_val; }
57+
bool has_value() const { return true; }
58+
};
59+
template <class T> struct Exp5 : private storage<T> {
60+
T *valptr() { return &this->m_val; }
61+
bool has_value() const { return true; }
62+
};
63+
64+
consteval int count_named(meta::info cls, std::string_view name) {
65+
int n = 0;
66+
for (auto m : meta::members_of(cls, ctx))
67+
if (meta::has_identifier(m) && meta::identifier_of(m) == name)
68+
++n;
69+
return n;
70+
}
71+
72+
// members_of as the first instantiation trigger: must compile and enumerate
73+
// the member declarations. Control: the void specialization enumerates the
74+
// same surface as the well-formed-body int specialization.
75+
static_assert(meta::members_of(^^Exp1<void>, ctx).size() ==
76+
meta::members_of(^^Exp1<int>, ctx).size());
77+
static_assert(count_named(^^Exp1<void>, "valptr") == 1);
78+
static_assert(count_named(^^Exp1<void>, "has_value") == 1);
79+
80+
// Order-independence: ordinary use first, then the identical enumeration.
81+
Exp2<void> preinstantiated;
82+
static_assert(meta::members_of(^^Exp2<void>, ctx).size() ==
83+
meta::members_of(^^Exp1<void>, ctx).size());
84+
85+
// Other completing metafunctions as first triggers (same completion funnel).
86+
static_assert(meta::nonstatic_data_members_of(^^Exp3<void>, ctx).size() == 0);
87+
static_assert(meta::bases_of(^^Exp3<void>, ctx).size() == 1);
88+
static_assert(meta::is_complete_type(^^Exp4<void>));
89+
static_assert(meta::size_of(^^Exp5<void>) == 1); // just storage<void>::m_dummy
90+
91+
int main() {
92+
// Lazy semantics still instantiate bodies that ARE odr-used.
93+
Exp1<int> e;
94+
assert(e.valptr() != nullptr);
95+
assert(e.has_value());
96+
return 0;
97+
}

0 commit comments

Comments
 (0)