Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
cf96ccd
Refactor ConvertGenericCallExpr
lucic71 May 23, 2026
1c0fb3a
EmitArgBindings -> EmitHoistedArgs
lucic71 May 23, 2026
e98df0c
Drop default initialization fom CallInfo
lucic71 May 24, 2026
01765d8
Add prefix on param_name from construction
lucic71 May 24, 2026
dba4b3b
Pass CallInfo by rvalue reference
lucic71 May 24, 2026
5c1425d
Reorder CallArg fields by size
lucic71 May 24, 2026
06097f8
Reorder initialization
lucic71 May 24, 2026
27fd2af
Reorder CallInfo fields
lucic71 May 27, 2026
768b308
Use template syntax to declare rules for variadic functions
lucic71 May 25, 2026
60fc170
Add src rules for variadic functions
lucic71 May 27, 2026
08d2cc1
Add Rust rule counterpart
lucic71 May 27, 2026
c85429d
Add C rules for builtin mul overflow
lucic71 May 27, 2026
d2ac848
Read the is_variadic attribute on the other side
lucic71 May 27, 2026
8b98f29
Save CallExpr instead of callee in CallInfo
lucic71 May 27, 2026
91b69ea
Set is_libc_passthrough
lucic71 May 27, 2026
ba997cd
Use correct types for arguments
lucic71 May 27, 2026
108cdb7
Use declared type for each argument in passthrough mode
lucic71 May 27, 2026
f000eb5
Fix code
lucic71 May 27, 2026
70f5880
Missing part of c85429df5caa6474c2a9f1f9f2f5afc632e38ef0
lucic71 May 27, 2026
3bc0b4c
Fix is_libc_passthrough condition
lucic71 Jun 2, 2026
b4219d8
Merge branch 'master' into variadic-call
lucic71 Jun 4, 2026
c68dcee
Re-add paren for variadic arg
lucic71 Jun 4, 2026
680424e
Fix merge artifacts
lucic71 Jun 4, 2026
0c3a442
Add Mapper::IsLibcPassthrough
lucic71 Jun 4, 2026
582d5d3
Replace is_variadic with is_extern
lucic71 Jun 4, 2026
eb19e52
Add var args tests
lucic71 Jun 4, 2026
1736ec5
Delete non-portable test code parts
lucic71 Jun 4, 2026
042eac9
Merge branch 'master' into variadic-call
lucic71 Jun 4, 2026
addc4cd
Clang-format
lucic71 Jun 4, 2026
b513e56
Add safe rules for ioctl and fcntl
lucic71 Jun 22, 2026
4d8530c
Update unistd test expectation
lucic71 Jun 22, 2026
b1052cf
Use va instead of args
lucic71 Jun 22, 2026
e85dcbf
Add is_va_arg attribute in IR
lucic71 Jun 22, 2026
14db663
Consume is_va_arg attr from IR
lucic71 Jun 22, 2026
d45e143
Merge branch 'master' into variadic-call
lucic71 Jun 22, 2026
64c0f83
Update test
lucic71 Jun 22, 2026
0c14f66
Use getEmptyPack instead of creating new pack
lucic71 Jun 22, 2026
c4910dd
Add panic implementation for snpritnf
lucic71 Jun 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 55 additions & 12 deletions cpp2rust/converter/converter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1553,6 +1553,11 @@ bool Converter::VisitCallExpr(clang::CallExpr *expr) {
}

if (Mapper::Contains(expr->getCallee())) {
if (Mapper::IsLibcPassthrough(GetCalleeOrExpr(expr))) {
ConvertGenericCallExpr(expr);
return false;
}

auto **args = expr->getArgs();
auto num_args = expr->getNumArgs();
auto ctx = CollectRefBindingTempArgs(expr);
Expand Down Expand Up @@ -1627,20 +1632,20 @@ Converter::CallInfo Converter::CollectCallInfo(clang::CallExpr *expr) {
using Kind = CallArg::Kind;

CallInfo info;
info.callee = expr->getCallee();
info.expr = expr;
auto callee = GetCallee(expr);
unsigned arg_begin = 0;
if (auto op_call = llvm::dyn_cast<clang::CXXOperatorCallExpr>(expr)) {
if (op_call->getOperator() == clang::OO_Call) {
info.callee = op_call->getArg(0);
arg_begin = 1;
}
}

const auto *function =
expr->getCalleeDecl() ? expr->getCalleeDecl()->getAsFunction() : nullptr;
auto decl = expr->getCalleeDecl();
const auto *function = decl ? decl->getAsFunction() : nullptr;
const clang::FunctionProtoType *proto = nullptr;
if (!function) {
auto callee_ty = info.callee->getType().getDesugaredType(ctx_);
auto callee_ty = callee->getType().getDesugaredType(ctx_);
if (auto ptr_ty = callee_ty->getAs<clang::PointerType>()) {
proto = ptr_ty->getPointeeType()->getAs<clang::FunctionProtoType>();
}
Expand All @@ -1653,6 +1658,7 @@ Converter::CallInfo Converter::CollectCallInfo(clang::CallExpr *expr) {
function ? function->getNumParams() : proto->getNumParams();
info.is_variadic = function ? function->isVariadic() : proto->isVariadic();
info.is_fn_ptr_call = !function;
info.is_libc_passthrough = Mapper::IsLibcPassthrough(GetCalleeOrExpr(expr));

for (unsigned i = 0; i < num_named_params && i < num_args; ++i) {
auto *arg = expr->getArg(i + arg_begin);
Expand All @@ -1664,7 +1670,8 @@ Converter::CallInfo Converter::CollectCallInfo(clang::CallExpr *expr) {
: proto->getParamType(i),
.expr = arg,
.has_default = function && function->getParamDecl(i)->hasDefaultArg(),
.kind = IsLiteral(arg) ? Kind::Inline : Kind::Hoisted,
.kind = (IsLiteral(arg) || info.is_libc_passthrough) ? Kind::Inline
: Kind::Hoisted,
};
bool is_materialize = clang::isa<clang::MaterializeTemporaryExpr>(arg);
if (is_materialize && ca.param_type->isLValueReferenceType()) {
Expand Down Expand Up @@ -1720,7 +1727,9 @@ void Converter::EmitArgList(const CallInfo &info) {
using Kind = CallArg::Kind;
PushParen call_args(*this);

for (const auto &ca : info.args) {
for (unsigned i = 0; i < info.args.size(); i++) {
const auto &ca = info.args[i];

if (ca.has_default) {
StrCat("Some");
}
Expand All @@ -1736,6 +1745,10 @@ void Converter::EmitArgList(const CallInfo &info) {
break;
case Kind::Inline:
ConvertParamTy(ca.param_type, ca.expr);
if (info.is_libc_passthrough) {
StrCat(std::format(
"as {}", Mapper::GetParamType(GetCalleeOrExpr(info.expr), i)));
}
break;
}
}
Expand All @@ -1744,14 +1757,19 @@ void Converter::EmitArgList(const CallInfo &info) {
}

if (info.is_variadic) {
StrCat(token::kRef);
PushBracket push(*this);
if (!info.is_libc_passthrough) {
StrCat(token::kRef);
}
PushBracket push(*this, !info.is_libc_passthrough);
for (auto *arg : info.variadic_args) {
{
PushParen p(*this);
ConvertVariadicArg(arg);
}
StrCat(".into()", token::kComma);
if (!info.is_libc_passthrough) {
StrCat(".into()");
}
StrCat(token::kComma);
}
}
}
Expand All @@ -1760,10 +1778,14 @@ void Converter::EmitCall(CallInfo &&info) {
EmitHoistedArgs(info);

if (info.is_fn_ptr_call) {
EmitFnPtrCall(info.callee);
EmitFnPtrCall(GetCallee(info.expr));
} else if (info.is_libc_passthrough) {
auto *direct_callee = info.expr->getDirectCallee();
assert(direct_callee);
StrCat("libc::", direct_callee->getName());
} else {
PushExprKind push(*this, ExprKind::Callee);
Convert(info.callee);
Convert(GetCallee(info.expr));
}

EmitArgList(info);
Expand Down Expand Up @@ -4193,6 +4215,8 @@ std::string Converter::ConvertIRFragment(
.is_index_base = ph->is_index_base,
};
result += ConvertPlaceholder(expr, arg, ph_ctx);
} else if (std::get_if<TranslationRule::VaArgsFragment>(&frag)) {
result += ConvertVariadicTail(expr, all_args);
} else if (auto *mc =
std::get_if<std::unique_ptr<MethodCallFragment>>(&frag)) {
result += ConvertMappedMethodCall(expr, **mc, args, num_args, ctx);
Expand All @@ -4202,6 +4226,25 @@ std::string Converter::ConvertIRFragment(
return result;
}

std::string
Converter::ConvertVariadicTail(clang::Expr *expr,
const std::vector<clang::Expr *> &all_args) {
const auto *tgt_ir = Mapper::GetExprRule(GetCalleeOrExpr(expr));
unsigned fixed = tgt_ir ? tgt_ir->params.size() : 0;

Buffer buf(*this);
StrCat("&[");
for (unsigned i = fixed; i < all_args.size(); ++i) {
{
PushParen p(*this);
ConvertVariadicArg(all_args[i]);
}
StrCat(".into()", token::kComma);
}
StrCat("]");
return std::move(buf).str();
}

std::string Converter::AccessLValueObject(clang::MemberExpr *member) {
auto *object = member->getBase();
auto type = object->getType();
Expand Down
6 changes: 5 additions & 1 deletion cpp2rust/converter/converter.h
Original file line number Diff line number Diff line change
Expand Up @@ -245,9 +245,10 @@ class Converter : public clang::RecursiveASTVisitor<Converter> {
struct CallInfo {
std::vector<CallArg> args;
std::vector<clang::Expr *> variadic_args;
clang::Expr *callee;
clang::CallExpr *expr;
bool is_variadic;
bool is_fn_ptr_call;
bool is_libc_passthrough;
};

CallInfo CollectCallInfo(clang::CallExpr *expr);
Expand Down Expand Up @@ -573,6 +574,9 @@ class Converter : public clang::RecursiveASTVisitor<Converter> {
std::string ConvertPlaceholder(clang::Expr *expr, clang::Expr *arg,
const PlaceholderCtx &ph_ctx);

std::string ConvertVariadicTail(clang::Expr *expr,
const std::vector<clang::Expr *> &all_args);

virtual std::string ConvertMappedMethodCall(
clang::Expr *expr, const TranslationRule::MethodCallFragment &mc,
clang::Expr **args, unsigned num_args, TempMaterializationCtx *ctx);
Expand Down
9 changes: 9 additions & 0 deletions cpp2rust/converter/converter_lib.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,15 @@ BuildUnifiedArgs(clang::Expr *expr, clang::Expr **args, unsigned num_args) {
return all_args;
}

clang::Expr *GetCallee(clang::CallExpr *expr) {
if (auto op_call = clang::dyn_cast<clang::CXXOperatorCallExpr>(expr)) {
if (op_call->getOperator() == clang::OO_Call) {
return op_call->getArg(0);
}
}
return expr->getCallee();
}

clang::Expr *GetCalleeOrExpr(clang::Expr *expr) {
if (auto *call = clang::dyn_cast<clang::CallExpr>(expr)) {
return call->getCallee();
Expand Down
2 changes: 2 additions & 0 deletions cpp2rust/converter/converter_lib.h
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ void ForEachTemplateArgument(

clang::Expr *GetCallObject(clang::CallExpr *expr);

clang::Expr *GetCallee(clang::CallExpr *expr);

clang::Expr *GetCalleeOrExpr(clang::Expr *expr);

bool HasReceiver(clang::Expr *expr);
Expand Down
19 changes: 19 additions & 0 deletions cpp2rust/converter/mapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,19 @@ const TranslationRule::ExprRule *GetExprRule(const clang::Expr *expr) {
return search(expr);
}

bool IsLibcPassthrough(const clang::Expr *expr) {
const auto *tgt_ir = GetExprRule(expr);
if (tgt_ir == nullptr || !tgt_ir->body.empty() || !tgt_ir->is_extern) {
return false;
}
const auto *ref =
clang::dyn_cast<clang::DeclRefExpr>(expr->IgnoreParenImpCasts());
const auto *decl = ref != nullptr ? ref->getDecl() : nullptr;
return decl != nullptr &&
decl->getASTContext().getSourceManager().isInSystemHeader(
decl->getLocation());
}

std::string MapFunctionName(const clang::FunctionDecl *decl) {
assert(decl);
if (!IsUserDefinedDecl(decl) &&
Expand Down Expand Up @@ -920,6 +933,12 @@ std::string ToString(const clang::NamedDecl *decl) {
}
os << ToString(func_decl->getParamDecl(i)->getType());
}
if (func_decl->isVariadic()) {
if (func_decl->getNumParams()) {
os << ", ";
}
os << "...";
}
os << ')';

if (const auto *method_decl =
Expand Down
1 change: 1 addition & 0 deletions cpp2rust/converter/mapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ bool Contains(const clang::Expr *expr);
std::string Map(clang::QualType qual_type);
std::string MapInitializer(clang::QualType qual_type);
const TranslationRule::ExprRule *GetExprRule(const clang::Expr *expr);
bool IsLibcPassthrough(const clang::Expr *expr);
std::string MapFunctionName(const clang::FunctionDecl *decl);
std::string InstantiateTemplate(const clang::Expr *expr, unsigned n);
bool ReturnsPointer(const clang::Expr *expr);
Expand Down
16 changes: 14 additions & 2 deletions cpp2rust/converter/translation_rule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ std::vector<BodyFragment> ParseBodyFragmentsJSON(const llvm::json::Array &arr) {
} else if (auto *mc = frag_obj->getObject("method_call")) {
result.push_back(std::make_unique<MethodCallFragment>(
ParseMethodCallFragmentJSON(*mc)));
} else if (frag_obj->get("va_args")) {
result.push_back(VaArgsFragment{});
}
}
return result;
Expand All @@ -104,11 +106,17 @@ ExprRule ParseExprRuleJSON(const llvm::json::Object &obj) {
}
}

if (auto *rt = obj.getObject("return_type"))
if (auto *rt = obj.getObject("return_type")) {
ir.return_type = ParseTypeInfoJSON(*rt);
}

if (auto ms = obj.getBoolean("multi_statement"))
if (auto ms = obj.getBoolean("multi_statement")) {
ir.multi_statement = *ms;
}

if (auto v = obj.getBoolean("is_extern")) {
ir.is_extern = *v;
}

if (auto *generics = obj.getObject("generics")) {
for (auto &[key, val] : *generics) {
Expand Down Expand Up @@ -222,6 +230,8 @@ void BodyFragmentDump(const BodyFragment &frag) {
p->dump();
} else if (auto *g = std::get_if<GenericFragment>(&frag)) {
g->dump();
} else if (auto *v = std::get_if<VaArgsFragment>(&frag)) {
v->dump();
} else if (auto *mc =
std::get_if<std::unique_ptr<MethodCallFragment>>(&frag)) {
(*mc)->dump();
Expand All @@ -232,6 +242,8 @@ void BodyFragmentDump(const BodyFragment &frag) {

void TextFragment::dump() const { log() << " text: \"" << text << "\"\n"; }

void VaArgsFragment::dump() const { log() << " va_args\n"; }

void PlaceholderFragment::dump() const {
log() << " placeholder: " << n;
switch (access) {
Expand Down
7 changes: 6 additions & 1 deletion cpp2rust/converter/translation_rule.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,15 @@ struct GenericFragment {
void dump() const;
};

struct VaArgsFragment {
void dump() const;
};

struct MethodCallFragment; // forward declaration

using BodyFragment =
std::variant<TextFragment, PlaceholderFragment, GenericFragment,
std::unique_ptr<MethodCallFragment>>;
VaArgsFragment, std::unique_ptr<MethodCallFragment>>;

struct MethodCallFragment {
std::vector<BodyFragment> receiver;
Expand Down Expand Up @@ -71,6 +75,7 @@ struct ExprRule {
std::vector<std::vector<std::string>> generics; // "T1" -> ["Ord", "Clone"]
std::vector<BodyFragment> body;
bool multi_statement = false;
bool is_extern = false;

void dump() const;
void validate(const std::string &name) const;
Expand Down
14 changes: 12 additions & 2 deletions cpp2rust/cpp_rule_preprocessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,9 @@ class Callback : public clang::ast_matchers::MatchFinder::MatchCallback {
createTemplateArguments(clang::TemplateDecl *decl,
llvm::SmallVectorImpl<clang::TemplateArgument> &out) {
for (clang::NamedDecl *param : *decl->getTemplateParameters()) {
if (llvm::isa<clang::TemplateTypeParmDecl>(param)) {
if (param->isTemplateParameterPack()) {
out.emplace_back(clang::TemplateArgument::getEmptyPack());
} else if (llvm::isa<clang::TemplateTypeParmDecl>(param)) {
clang::RecordDecl *rdecl = createRecordDecl(param->getName());
clang::QualType type =
sema_->Context.getTagType(clang::ElaboratedTypeKeyword::None,
Expand Down Expand Up @@ -414,7 +416,14 @@ class Callback : public clang::ast_matchers::MatchFinder::MatchCallback {
clang::OverloadCandidateSet &candidates) {
clang::LookupResult decls(*sema_, name, loc_,
clang::Sema::LookupOrdinaryName);
sema_->LookupQualifiedName(decls, sema_->getStdNamespace());
if (clang::NamespaceDecl *std_ns = sema_->getStdNamespace()) {
sema_->LookupQualifiedName(decls, std_ns);
}
if (decls.empty()) {
decls.clear();
sema_->LookupQualifiedName(decls,
sema_->Context.getTranslationUnitDecl());
}
for (auto *ndecl : decls) {
if (auto *candidate = createCandidate(ndecl, callArgs, explicitTArgs)) {
sema_->AddOverloadCandidate(
Expand Down Expand Up @@ -578,6 +587,7 @@ class Callback : public clang::ast_matchers::MatchFinder::MatchCallback {
cxxConstructorNameLookup(rule->getReturnType(), callArgs, candidates);
break;
case LookupKind::ADL:
regularNameLookup(callArgs, &explicitTArgs, name, candidates);
adlLookup(callArgs, name, candidates);
break;
}
Expand Down
26 changes: 21 additions & 5 deletions rule-preprocessor/src/ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ pub struct FnIr {
pub params: Option<BTreeMap<String, TypeInfo>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub return_type: Option<TypeInfo>,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_extern: Option<bool>,
}

impl FnIr {
Expand Down Expand Up @@ -106,7 +108,10 @@ impl FnIr {
1,
&format!("Rule {name} generics"),
);
assert!(!self.body.is_empty(), "Rule {name}: body must not be empty");
assert!(
self.is_extern == Some(true) || !self.body.is_empty(),
"Rule {name}: body must not be empty"
);
}
}

Expand All @@ -120,10 +125,21 @@ pub struct TypeIr {
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum BodyFragment {
Text { text: String },
Placeholder { placeholder: PlaceholderInner },
Generic { generic: i32 },
MethodCall { method_call: MethodCallInner },
Text {
text: String,
},
Placeholder {
placeholder: PlaceholderInner,
},
Generic {
generic: i32,
},
MethodCall {
method_call: MethodCallInner,
},
VaArgs {
va_args: std::marker::PhantomData<()>,
},
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
Expand Down
Loading
Loading