Skip to content

Commit ed39812

Browse files
Cfretz244claude
andcommitted
[clang][P2996] Make parameter-name queries independent of instantiation state
The P3096 consistency walk in getParameterName runs over the function's redeclaration chain and reports no identifier when redeclarations disagree. For an instantiated member, however, instantiating the out-of-line definition REPLACES the parameters on the same FunctionDecl -- no redeclaration is added -- so the walk saw exactly one name: whichever redeclaration happened to be instantiated last. The same query on the same entity then answered differently depending on instantiation state, and two translation units reflecting the same entity could disagree (field shape: Eigen declares DenseBase::setConstant(const Scalar& value) and defines it with 'val'; identifier_of flipped between the two depending on whether binding lambdas had odr-used the member). Walk the template instantiation pattern's declaration chain instead, which carries the in-class declaration and the out-of-line definition regardless of instantiation state. Skipped when the pattern contains a parameter pack (its parameter list does not line up index-for-index with the instantiation's; pack-substituted parameters are already filtered earlier). An inconsistently-named parameter now deterministically has no identifier; consistently-named parameters keep their name. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 837da39 commit ed39812

2 files changed

Lines changed: 68 additions & 0 deletions

File tree

clang/lib/AST/ExprConstantMeta.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1099,6 +1099,24 @@ static bool getParameterName(ParmVarDecl *PVD, std::string &Out) {
10991099
// a function declaration, since the DeclContext is not the function but the
11001100
// TranslationUnitDecl.
11011101
FunctionDecl *FD = cast<FunctionDecl>(PVD->getDeclContext());
1102+
1103+
// For an instantiated member, instantiating the out-of-line definition
1104+
// REPLACES the parameters on the same FunctionDecl (no redeclaration is
1105+
// added), so walking the instantiation's chain reads whichever
1106+
// redeclaration happened to be instantiated last -- the answer would
1107+
// depend on instantiation state and could differ between translation
1108+
// units reflecting the same entity. The template pattern carries the
1109+
// full declaration chain regardless of instantiation state, so walk
1110+
// that instead. (Skipped when the pattern contains a parameter pack:
1111+
// its parameter list does not line up index-for-index with the
1112+
// instantiation's, and pack-substituted parameters were already
1113+
// filtered above.)
1114+
if (FunctionDecl *Pattern = FD->getTemplateInstantiationPattern();
1115+
Pattern && llvm::none_of(Pattern->parameters(), [](const ParmVarDecl *P) {
1116+
return P->isParameterPack();
1117+
}))
1118+
FD = Pattern;
1119+
11021120
FD = FD->getMostRecentDecl();
11031121
PVD = FD->getParamDecl(ParamIdx);
11041122

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
// ADDITIONAL_COMPILE_FLAGS: -freflection-latest -fparameter-reflection
9+
10+
// <experimental/meta>
11+
12+
// RUN: %{build}
13+
// RUN: %{exec} %t.exe
14+
15+
// Parameter-name queries must not depend on instantiation state. A member
16+
// whose in-class declaration and out-of-line definition name a parameter
17+
// differently has NO consistent name; instantiating the definition (which
18+
// replaces the parameters on the instantiated FunctionDecl in place) must
19+
// not change the answer. The consistency walk goes through the template
20+
// pattern's full declaration chain.
21+
22+
#include <experimental/meta>
23+
#include <string_view>
24+
25+
using namespace std::meta;
26+
27+
template <class T> struct S {
28+
void f(int value); // inconsistent: the definition says "val"
29+
void g(int width); // consistent across declaration + definition
30+
};
31+
template <class T> void S<T>::f(int val) {}
32+
template <class T> void S<T>::g(int width) {}
33+
34+
// Instantiate the definitions BEFORE the queries: pre-fix this flipped f's
35+
// reported parameter name from "value" to "val".
36+
template void S<int>::f(int);
37+
template void S<int>::g(int);
38+
39+
consteval info param0(std::string_view name) {
40+
for (auto m : members_of(^^S<int>, access_context::unchecked()))
41+
if (is_function(m) && has_identifier(m) && identifier_of(m) == name)
42+
return parameters_of(m)[0];
43+
return {};
44+
}
45+
46+
static_assert(!has_identifier(param0("f")));
47+
static_assert(has_identifier(param0("g")));
48+
static_assert(identifier_of(param0("g")) == "width");
49+
50+
int main(int, char**) { return 0; }

0 commit comments

Comments
 (0)