From 604654e95997c7809ab60dc5a41bc6209476b2d3 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Wed, 21 Oct 2026 14:26:26 +0100 Subject: [PATCH 01/11] Key and match rules returning an empty init list --- cpp2rust/converter/rules/expr_matcher.cpp | 37 +++++++++++++++++++++-- cpp2rust/converter/rules/rule_keys.cpp | 3 ++ rules/pair/src.cpp | 2 ++ rules/pair/tgt_refcount.rs | 7 +++++ rules/pair/tgt_unsafe.rs | 4 +++ tests/unit/pair.cpp | 3 ++ 6 files changed, 53 insertions(+), 3 deletions(-) diff --git a/cpp2rust/converter/rules/expr_matcher.cpp b/cpp2rust/converter/rules/expr_matcher.cpp index c08fed92c..5d8ecbb46 100644 --- a/cpp2rust/converter/rules/expr_matcher.cpp +++ b/cpp2rust/converter/rules/expr_matcher.cpp @@ -342,6 +342,28 @@ MatchResult matchFunction(clang::Sema &sema, const clang::Expr *use, return llvm::ArrayRef(); } +bool constructsReturnType(const clang::Expr *returned, clang::QualType ret, + const ResolvedDecl &usage_decl) { + auto list = + llvm::dyn_cast(RuleDecls::SkipImplicit(returned)); + if (!list || list->getType()->isRecordType()) { + return false; + } + auto record = ret->getAsCXXRecordDecl(); + if (!record) { + return false; + } + if (auto decl = std::get_if(&usage_decl)) { + auto ctor = llvm::dyn_cast(*decl); + return ctor && ctor->getParent()->getCanonicalDecl() == + record->getCanonicalDecl(); + } + if (auto aggregate = std::get_if(&usage_decl)) { + return RuleKeys::GetTypeKey(ret) == aggregate->type; + } + return false; +} + MatchResult matchTemplate(clang::Sema &sema, const clang::Expr *use, const ResolvedDecl &usage_decl, clang::FunctionTemplateDecl *rule) { @@ -355,7 +377,9 @@ MatchResult matchTemplate(clang::Sema &sema, const clang::Expr *use, } if (std::holds_alternative(usage_decl)) { auto returned = RuleDecls::GetReturned(rule->getTemplatedDecl()); - if (getResolvedDecl(ctx, returned, /*must_resolve=*/false) != usage_decl) { + if (getResolvedDecl(ctx, returned, /*must_resolve=*/false) != usage_decl && + !constructsReturnType(returned, specialization->getReturnType(), + usage_decl)) { return std::nullopt; } checkCallable(sema, use, rule->getTemplatedDecl()); @@ -363,8 +387,15 @@ MatchResult matchTemplate(clang::Sema &sema, const clang::Expr *use, } auto returned = substituteSpecializationInRuleTmpl(sema, rule, specialization); - if (!returned || - getResolvedDecl(ctx, returned, /*must_resolve=*/true) != usage_decl) { + if (!returned) { + return std::nullopt; + } + if (constructsReturnType(returned, specialization->getReturnType(), + usage_decl)) { + checkCallable(sema, use, rule->getTemplatedDecl()); + return specialization->getTemplateSpecializationArgs()->asArray(); + } + if (getResolvedDecl(ctx, returned, /*must_resolve=*/true) != usage_decl) { return std::nullopt; } checkCallable(sema, use, rule->getTemplatedDecl()); diff --git a/cpp2rust/converter/rules/rule_keys.cpp b/cpp2rust/converter/rules/rule_keys.cpp index e85143cf0..419e74e3f 100644 --- a/cpp2rust/converter/rules/rule_keys.cpp +++ b/cpp2rust/converter/rules/rule_keys.cpp @@ -231,6 +231,9 @@ std::vector GetExprKeys(clang::ASTContext &ctx, return keys; } auto key = getExprKey(ctx, returned); + if (!key && llvm::isa(returned)) { + key = getConstructorKey(getParent(rule->getAsFunction()->getReturnType())); + } assert(key && "expression rule has no key"); return {*key}; } diff --git a/rules/pair/src.cpp b/rules/pair/src.cpp index f085066f6..6ab00058e 100644 --- a/rules/pair/src.cpp +++ b/rules/pair/src.cpp @@ -78,3 +78,5 @@ template T1 &&f21(std::pair &&a0) { template T2 &&f22(std::pair &&a0) { return std::get<1>(std::move(a0)); } + +template std::pair f23() { return {}; } diff --git a/rules/pair/tgt_refcount.rs b/rules/pair/tgt_refcount.rs index 07757ab39..e1eddab20 100644 --- a/rules/pair/tgt_refcount.rs +++ b/rules/pair/tgt_refcount.rs @@ -92,3 +92,10 @@ fn f21(a0: (Value, Value)) -> Ptr { fn f22(a0: (Value, Value)) -> Ptr { a0.1.as_pointer() } + +fn f23() -> (Value, Value) { + ( + Rc::new(RefCell::new(T1::default())), + Rc::new(RefCell::new(T2::default())), + ) +} diff --git a/rules/pair/tgt_unsafe.rs b/rules/pair/tgt_unsafe.rs index 0604e0552..935982743 100644 --- a/rules/pair/tgt_unsafe.rs +++ b/rules/pair/tgt_unsafe.rs @@ -69,3 +69,7 @@ unsafe fn f21(a0: &mut (T1, T2)) -> *mut T1 { unsafe fn f22(a0: &mut (T1, T2)) -> *mut T2 { &mut a0.1 } + +unsafe fn f23() -> (T1, T2) { + <(T1, T2)>::default() +} diff --git a/tests/unit/pair.cpp b/tests/unit/pair.cpp index c603bf5cf..ddffbe8bf 100644 --- a/tests/unit/pair.cpp +++ b/tests/unit/pair.cpp @@ -12,6 +12,9 @@ int main() { std::pair p3 = std::make_pair(5, 6); assert(p3.first == 5.0); assert(p3.second == 6); + std::pair p4{}; + assert(p4.first == 0.0); + assert(p4.second == 0); const int arr[2] = {1, 2}; unsigned n = 2; std::vector> const_ptr_pairs(1); From d6cca45bbbd5115855874a2fca8baf780accc7ea Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Wed, 21 Oct 2026 20:08:42 +0100 Subject: [PATCH 02/11] Key rules returning an init list on the class of their return type --- cpp2rust/rule_preprocessing/cpp_rule_indexer.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cpp2rust/rule_preprocessing/cpp_rule_indexer.cpp b/cpp2rust/rule_preprocessing/cpp_rule_indexer.cpp index 346045ec5..006e86b6c 100644 --- a/cpp2rust/rule_preprocessing/cpp_rule_indexer.cpp +++ b/cpp2rust/rule_preprocessing/cpp_rule_indexer.cpp @@ -271,6 +271,11 @@ void IndexRuleFile(clang::ASTContext &ctx, const std::string &dir_name, if (key.empty()) { key = ClassOfParameters(decl); } + if (auto returned = RuleExpr(decl); + key.empty() && returned && + llvm::isa(returned->IgnoreImplicit())) { + key = ClassOf(decl->getAsFunction()->getReturnType()); + } bool any_class = key.empty() && llvm::isa(decl); if (any_class) { key = RulesLoader::kAnyClassKey; From 05594328a7cb6e063b8195f4d9e79f56bc0274ba Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Wed, 21 Oct 2026 20:15:05 +0100 Subject: [PATCH 03/11] Prove init list rules with std::atomic --- rules/atomic/src.cpp | 12 ++++++++++++ rules/atomic/tgt_unsafe.rs | 14 ++++++++++++++ rules/pair/src.cpp | 2 -- rules/pair/tgt_refcount.rs | 7 ------- rules/pair/tgt_unsafe.rs | 4 ---- tests/unit/atomic.cpp | 9 +++++++++ tests/unit/pair.cpp | 3 --- 7 files changed, 35 insertions(+), 16 deletions(-) create mode 100644 rules/atomic/src.cpp create mode 100644 rules/atomic/tgt_unsafe.rs create mode 100644 tests/unit/atomic.cpp diff --git a/rules/atomic/src.cpp b/rules/atomic/src.cpp new file mode 100644 index 000000000..bb292c03f --- /dev/null +++ b/rules/atomic/src.cpp @@ -0,0 +1,12 @@ +// Copyright (c) 2022-present INESC-ID. +// Distributed under the MIT license that can be found in the LICENSE file. + +#include + +template using t1 = std::atomic; + +template std::atomic f1() { return {}; } + +template T1 f2(const std::atomic &a0) { + return static_cast(a0); +} diff --git a/rules/atomic/tgt_unsafe.rs b/rules/atomic/tgt_unsafe.rs new file mode 100644 index 000000000..27fe1c87f --- /dev/null +++ b/rules/atomic/tgt_unsafe.rs @@ -0,0 +1,14 @@ +// Copyright (c) 2022-present INESC-ID. +// Distributed under the MIT license that can be found in the LICENSE file. + +fn t1() -> T1 { + T1::default() +} + +unsafe fn f1() -> T1 { + T1::default() +} + +unsafe fn f2(a0: T1) -> T1 { + a0 +} diff --git a/rules/pair/src.cpp b/rules/pair/src.cpp index 6ab00058e..f085066f6 100644 --- a/rules/pair/src.cpp +++ b/rules/pair/src.cpp @@ -78,5 +78,3 @@ template T1 &&f21(std::pair &&a0) { template T2 &&f22(std::pair &&a0) { return std::get<1>(std::move(a0)); } - -template std::pair f23() { return {}; } diff --git a/rules/pair/tgt_refcount.rs b/rules/pair/tgt_refcount.rs index e1eddab20..07757ab39 100644 --- a/rules/pair/tgt_refcount.rs +++ b/rules/pair/tgt_refcount.rs @@ -92,10 +92,3 @@ fn f21(a0: (Value, Value)) -> Ptr { fn f22(a0: (Value, Value)) -> Ptr { a0.1.as_pointer() } - -fn f23() -> (Value, Value) { - ( - Rc::new(RefCell::new(T1::default())), - Rc::new(RefCell::new(T2::default())), - ) -} diff --git a/rules/pair/tgt_unsafe.rs b/rules/pair/tgt_unsafe.rs index 935982743..0604e0552 100644 --- a/rules/pair/tgt_unsafe.rs +++ b/rules/pair/tgt_unsafe.rs @@ -69,7 +69,3 @@ unsafe fn f21(a0: &mut (T1, T2)) -> *mut T1 { unsafe fn f22(a0: &mut (T1, T2)) -> *mut T2 { &mut a0.1 } - -unsafe fn f23() -> (T1, T2) { - <(T1, T2)>::default() -} diff --git a/tests/unit/atomic.cpp b/tests/unit/atomic.cpp new file mode 100644 index 000000000..b8a3c33a9 --- /dev/null +++ b/tests/unit/atomic.cpp @@ -0,0 +1,9 @@ +#include +#include + +int main() { + std::atomic a{}; + int v = a; + assert(v == 0); + return 0; +} diff --git a/tests/unit/pair.cpp b/tests/unit/pair.cpp index ddffbe8bf..c603bf5cf 100644 --- a/tests/unit/pair.cpp +++ b/tests/unit/pair.cpp @@ -12,9 +12,6 @@ int main() { std::pair p3 = std::make_pair(5, 6); assert(p3.first == 5.0); assert(p3.second == 6); - std::pair p4{}; - assert(p4.first == 0.0); - assert(p4.second == 0); const int arr[2] = {1, 2}; unsigned n = 2; std::vector> const_ptr_pairs(1); From 7edddbf7d188d2b19a4bdfa8ff581d2d527b6cbc Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Wed, 21 Oct 2026 20:19:20 +0100 Subject: [PATCH 04/11] Use the primary std::atomic template in the test --- rules/atomic/tgt_unsafe.rs | 2 +- tests/unit/atomic.cpp | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/rules/atomic/tgt_unsafe.rs b/rules/atomic/tgt_unsafe.rs index 27fe1c87f..76c75b888 100644 --- a/rules/atomic/tgt_unsafe.rs +++ b/rules/atomic/tgt_unsafe.rs @@ -9,6 +9,6 @@ unsafe fn f1() -> T1 { T1::default() } -unsafe fn f2(a0: T1) -> T1 { +unsafe fn f2(a0: T1) -> T1 { a0 } diff --git a/tests/unit/atomic.cpp b/tests/unit/atomic.cpp index b8a3c33a9..0a171d37d 100644 --- a/tests/unit/atomic.cpp +++ b/tests/unit/atomic.cpp @@ -1,9 +1,15 @@ #include #include +struct Pair { + int x; + int y; +}; + int main() { - std::atomic a{}; - int v = a; - assert(v == 0); + std::atomic a{}; + Pair v = a; + assert(v.x == 0); + assert(v.y == 0); return 0; } From 67180fa8be9957cc624298a83038d905a182fe09 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Wed, 21 Oct 2026 20:19:30 +0100 Subject: [PATCH 05/11] Use a lock-free struct in the atomic test --- tests/unit/atomic.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/tests/unit/atomic.cpp b/tests/unit/atomic.cpp index 0a171d37d..8e0c58777 100644 --- a/tests/unit/atomic.cpp +++ b/tests/unit/atomic.cpp @@ -1,15 +1,13 @@ #include #include -struct Pair { - int x; - int y; +struct Counter { + int n; }; int main() { - std::atomic a{}; - Pair v = a; - assert(v.x == 0); - assert(v.y == 0); + std::atomic a{}; + Counter v = a; + assert(v.n == 0); return 0; } From 4df7b090cac05e2e5b10884527b71f4ad35e846c Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Wed, 21 Oct 2026 20:34:20 +0100 Subject: [PATCH 06/11] Try binding parameter firstr --- cpp2rust/converter/rules/registry.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cpp2rust/converter/rules/registry.cpp b/cpp2rust/converter/rules/registry.cpp index 41d8eb3bb..405919819 100644 --- a/cpp2rust/converter/rules/registry.cpp +++ b/cpp2rust/converter/rules/registry.cpp @@ -417,7 +417,10 @@ Matcher::Match Search(clang::ASTContext &ctx, } auto bindings = getBindings(match.rule_decl->decl, match.match.args); if (auto generic = match.rule_decl->rule.generic) { - return Search(ctx, bindings.at(generic - 1).getAsType()); + if (auto bound = Search(ctx, bindings.at(generic - 1).getAsType()); + bound.first) { + return bound; + } } return {&match.rule_decl->rule, std::move(bindings)}; } From 493d8a843047376375b1cc89210f9f39a006287a Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Wed, 21 Oct 2026 20:34:44 +0100 Subject: [PATCH 07/11] Key on constructor key on dependent type --- cpp2rust/converter/rules/rule_keys.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cpp2rust/converter/rules/rule_keys.cpp b/cpp2rust/converter/rules/rule_keys.cpp index 419e74e3f..622171902 100644 --- a/cpp2rust/converter/rules/rule_keys.cpp +++ b/cpp2rust/converter/rules/rule_keys.cpp @@ -135,6 +135,10 @@ std::optional getExprKey(clang::ASTContext &ctx, rewritten->getOperator())); } if (auto cast = llvm::dyn_cast(expr)) { + if (auto key = getConstructorKey(getParent(cast->getSubExpr()->getType())); + key && cast->isTypeDependent()) { + return key; + } return getExprKey(ctx, cast->getSubExpr()); } if (auto construct = llvm::dyn_cast(expr)) { From 161825788630dc1de2ef95b0b80e0683ff6be51a Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Wed, 21 Oct 2026 20:34:57 +0100 Subject: [PATCH 08/11] Update tests --- tests/unit/out/refcount/atomic.rs | 25 +++++++++++++++++++++++++ tests/unit/out/unsafe/atomic.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 tests/unit/out/refcount/atomic.rs create mode 100644 tests/unit/out/unsafe/atomic.rs diff --git a/tests/unit/out/refcount/atomic.rs b/tests/unit/out/refcount/atomic.rs new file mode 100644 index 000000000..c9ff39deb --- /dev/null +++ b/tests/unit/out/refcount/atomic.rs @@ -0,0 +1,25 @@ +extern crate libcc2rs; +use libcc2rs::*; +use std::cell::RefCell; +use std::collections::BTreeMap; +use std::io::prelude::*; +use std::io::{Read, Seek, Write}; +use std::os::fd::AsFd; +use std::rc::{Rc, Weak}; +#[derive(Clone, Record, ByteRepr, VaArg, FnPtrArg, Default)] +#[byte_size(4)] +pub struct Counter { + #[offset(0)] + pub n: i32, +} +pub fn main() { + __cpp2rust_init_globals(); + std::process::exit(main_0()); +} +fn main_0() -> i32 { + let a: Value = Rc::new(RefCell::new(Counter::default())); + let mut v: Counter = ((*a.borrow()) as Counter); + assert!((v.n == 0)); + return 0; +} +pub fn __cpp2rust_init_globals() {} diff --git a/tests/unit/out/unsafe/atomic.rs b/tests/unit/out/unsafe/atomic.rs new file mode 100644 index 000000000..eb052fa50 --- /dev/null +++ b/tests/unit/out/unsafe/atomic.rs @@ -0,0 +1,26 @@ +extern crate libc; +use libc::*; +extern crate libcc2rs; +use libcc2rs::*; +use std::collections::BTreeMap; +use std::io::{Read, Seek, Write}; +use std::os::fd::{AsFd, FromRawFd, IntoRawFd}; +use std::rc::Rc; +#[repr(C)] +#[derive(Copy, Clone, VaArg, FnPtrArg, Default)] +pub struct Counter { + pub n: i32, +} +pub fn main() { + unsafe { + __cpp2rust_init_globals(); + std::process::exit(main_0() as i32); + } +} +unsafe fn main_0() -> i32 { + let mut a: Counter = Counter::default(); + let mut v: Counter = (a as Counter); + assert!(((v.n) == (0))); + return 0; +} +pub unsafe fn __cpp2rust_init_globals() {} From 1a3958076521aacfea9f5174b4ac04bd23f797ff Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Wed, 21 Oct 2026 20:40:22 +0100 Subject: [PATCH 09/11] Trim return type --- cpp2rust/converter/converter.cpp | 4 ++-- cpp2rust/converter/rules/expr_matcher.cpp | 4 ++-- tests/unit/out/refcount/atomic.rs | 2 +- tests/unit/out/unsafe/atomic.rs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cpp2rust/converter/converter.cpp b/cpp2rust/converter/converter.cpp index 18b9bd668..9fb1956a1 100644 --- a/cpp2rust/converter/converter.cpp +++ b/cpp2rust/converter/converter.cpp @@ -2465,8 +2465,8 @@ void Converter::ConvertIntegralToBooleanCast(clang::ImplicitCastExpr *expr) { bool Converter::IsCastRedundantInRust(clang::Expr *expr, clang::QualType target_type) { auto target = GetUnsafeTypeAsString(target_type); - if (const auto *rule = RuleRegistry::GetExprRule(ctx_, expr)) { - return rule->return_type.type == target; + if (RuleRegistry::GetExprRule(ctx_, expr)) { + return Trim(Mapper::GetReturnType(*this, expr)) == target; } return GetUnsafeTypeAsString(expr->getType()) == target; } diff --git a/cpp2rust/converter/rules/expr_matcher.cpp b/cpp2rust/converter/rules/expr_matcher.cpp index 5d8ecbb46..876c689e7 100644 --- a/cpp2rust/converter/rules/expr_matcher.cpp +++ b/cpp2rust/converter/rules/expr_matcher.cpp @@ -355,8 +355,8 @@ bool constructsReturnType(const clang::Expr *returned, clang::QualType ret, } if (auto decl = std::get_if(&usage_decl)) { auto ctor = llvm::dyn_cast(*decl); - return ctor && ctor->getParent()->getCanonicalDecl() == - record->getCanonicalDecl(); + return ctor && + ctor->getParent()->getCanonicalDecl() == record->getCanonicalDecl(); } if (auto aggregate = std::get_if(&usage_decl)) { return RuleKeys::GetTypeKey(ret) == aggregate->type; diff --git a/tests/unit/out/refcount/atomic.rs b/tests/unit/out/refcount/atomic.rs index c9ff39deb..d5576c337 100644 --- a/tests/unit/out/refcount/atomic.rs +++ b/tests/unit/out/refcount/atomic.rs @@ -18,7 +18,7 @@ pub fn main() { } fn main_0() -> i32 { let a: Value = Rc::new(RefCell::new(Counter::default())); - let mut v: Counter = ((*a.borrow()) as Counter); + let mut v: Counter = (*a.borrow()).clone(); assert!((v.n == 0)); return 0; } diff --git a/tests/unit/out/unsafe/atomic.rs b/tests/unit/out/unsafe/atomic.rs index eb052fa50..ce9dbc817 100644 --- a/tests/unit/out/unsafe/atomic.rs +++ b/tests/unit/out/unsafe/atomic.rs @@ -19,7 +19,7 @@ pub fn main() { } unsafe fn main_0() -> i32 { let mut a: Counter = Counter::default(); - let mut v: Counter = (a as Counter); + let mut v: Counter = a; assert!(((v.n) == (0))); return 0; } From 49b22dbdccafbea14887aed28f7abdc7ee85d6a4 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Wed, 21 Oct 2026 20:48:18 +0100 Subject: [PATCH 10/11] Add todo --- rules/atomic/tgt_unsafe.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rules/atomic/tgt_unsafe.rs b/rules/atomic/tgt_unsafe.rs index 76c75b888..9ea5044d5 100644 --- a/rules/atomic/tgt_unsafe.rs +++ b/rules/atomic/tgt_unsafe.rs @@ -1,6 +1,8 @@ // Copyright (c) 2022-present INESC-ID. // Distributed under the MIT license that can be found in the LICENSE file. +// TODO: use proper atomic translation + fn t1() -> T1 { T1::default() } From 167832efe63b45efa038591995b1fa8fb02e3aeb Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Wed, 21 Oct 2026 22:45:48 +0100 Subject: [PATCH 11/11] Key member on the receiver --- cpp2rust/converter/rules/rule_keys.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cpp2rust/converter/rules/rule_keys.cpp b/cpp2rust/converter/rules/rule_keys.cpp index 622171902..c04f10ad8 100644 --- a/cpp2rust/converter/rules/rule_keys.cpp +++ b/cpp2rust/converter/rules/rule_keys.cpp @@ -32,7 +32,6 @@ const clang::Decl *getParent(clang::QualType type) { spec->getTemplateName().getAsTemplateDecl())) { return tmpl->getCanonicalDecl(); } - return nullptr; } auto record = type->getAsCXXRecordDecl(); if (!record) { @@ -149,6 +148,9 @@ std::optional getExprKey(clang::ASTContext &ctx, return getConstructorKey(getParent(construct->getTypeAsWritten())); } if (auto call = llvm::dyn_cast(expr)) { + if (llvm::isa(call)) { + return getExprKey(ctx, call->getCallee()); + } if (auto callee = call->getDirectCallee()) { return getDeclKey(callee); } @@ -174,7 +176,9 @@ std::optional getExprKey(clang::ASTContext &ctx, sm, ctx.getLangOpts()); return Unqualified{clang::DeclarationName(&ctx.Idents.get(macro))}; } - return getDeclKey(member->getMemberDecl()); + return getMemberKey( + getParent(member->getBase()->IgnoreImpCasts()->getType()), + member->getMemberDecl()->getDeclName()); } if (auto ref = llvm::dyn_cast(expr)) { return getDeclKey(ref->getDecl());