Skip to content

Commit 916ab3c

Browse files
authored
Translate concepts and requires expressions (#347)
They are evaluated by clang. Only the result of the evaluation is translated. Furthermore, static_assert is no-op because it's evaluated at compile time. Same for concept declaration.
1 parent 80bda18 commit 916ab3c

7 files changed

Lines changed: 303 additions & 25 deletions

File tree

‎cpp2rust/converter/converter.cpp‎

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1284,6 +1284,27 @@ bool Converter::VisitTypeAliasTemplateDecl(clang::TypeAliasTemplateDecl *) {
12841284
return false;
12851285
}
12861286

1287+
bool Converter::VisitStaticAssertDecl(clang::StaticAssertDecl *decl) {
1288+
auto *assert_expr = decl->getAssertExpr();
1289+
if (assert_expr->isValueDependent()) {
1290+
return false;
1291+
}
1292+
std::string condition;
1293+
if (assert_expr->getType()->isBooleanType() &&
1294+
IsRustConstEvaluableExpr(assert_expr)) {
1295+
condition = ToString(assert_expr);
1296+
} else {
1297+
bool value = false;
1298+
ENSURE(assert_expr->EvaluateAsBooleanCondition(value, ctx_));
1299+
condition = value ? keyword::kTrue : keyword::kFalse;
1300+
}
1301+
StrCat(std::format("const _: () = assert!({}{});", condition,
1302+
GetAssertMessageAsString(assert_expr, ctx_)));
1303+
return false;
1304+
}
1305+
1306+
bool Converter::VisitConceptDecl(clang::ConceptDecl *) { return false; }
1307+
12871308
static bool IsaSemiColonStmt(const clang::Stmt *stmt) {
12881309
switch (stmt->getStmtClass()) {
12891310
case clang::Stmt::IfStmtClass:
@@ -2275,30 +2296,6 @@ bool Converter::VisitCharacterLiteral(clang::CharacterLiteral *expr) {
22752296
return false;
22762297
}
22772298

2278-
std::string Converter::GetEscapedCharLiteral(char character) const {
2279-
switch (character) {
2280-
case '"':
2281-
return "\\\"";
2282-
case '\'':
2283-
return "\\'";
2284-
case '\\':
2285-
return "\\\\";
2286-
case '\n':
2287-
return "\\n";
2288-
case '\r':
2289-
return "\\r";
2290-
case '\t':
2291-
return "\\t";
2292-
case '\0':
2293-
return "\\0";
2294-
}
2295-
auto uc = static_cast<unsigned char>(character);
2296-
if (uc < 0x20 || uc >= 0x7F) {
2297-
return std::format("\\x{:02x}", uc);
2298-
}
2299-
return std::string(1, character);
2300-
}
2301-
23022299
std::string Converter::GetEscapedUTF8CharLiteral(clang::Expr *expr) const {
23032300
auto char_expr =
23042301
clang::dyn_cast<clang::CharacterLiteral>(expr->IgnoreCasts());
@@ -3718,6 +3715,21 @@ bool Converter::VisitUnaryExprOrTypeTraitExpr(
37183715
return false;
37193716
}
37203717

3718+
bool Converter::VisitConceptSpecializationExpr(
3719+
clang::ConceptSpecializationExpr *expr) {
3720+
assert(!expr->isValueDependent());
3721+
StrCat(expr->isSatisfied() ? keyword::kTrue : keyword::kFalse);
3722+
computed_expr_type_ = ComputedExprType::FreshValue;
3723+
return false;
3724+
}
3725+
3726+
bool Converter::VisitRequiresExpr(clang::RequiresExpr *expr) {
3727+
assert(!expr->isValueDependent());
3728+
StrCat(expr->isSatisfied() ? keyword::kTrue : keyword::kFalse);
3729+
computed_expr_type_ = ComputedExprType::FreshValue;
3730+
return false;
3731+
}
3732+
37213733
bool Converter::VisitTypeTraitExpr(clang::TypeTraitExpr *expr) {
37223734
clang::Expr::EvalResult result;
37233735
ENSURE(expr->EvaluateAsInt(result, ctx_));

‎cpp2rust/converter/converter.h‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,9 @@ class Converter : public clang::RecursiveASTVisitor<Converter> {
178178
virtual bool VisitTypeAliasDecl(clang::TypeAliasDecl *decl);
179179
virtual bool VisitTypeAliasTemplateDecl(clang::TypeAliasTemplateDecl *decl);
180180

181+
bool VisitStaticAssertDecl(clang::StaticAssertDecl *decl);
182+
bool VisitConceptDecl(clang::ConceptDecl *decl);
183+
181184
virtual bool VisitCompoundStmt(clang::CompoundStmt *stmt);
182185

183186
virtual bool VisitDeclStmt(clang::DeclStmt *stmt);
@@ -365,7 +368,6 @@ class Converter : public clang::RecursiveASTVisitor<Converter> {
365368

366369
virtual bool VisitCharacterLiteral(clang::CharacterLiteral *expr);
367370

368-
std::string GetEscapedCharLiteral(char character) const;
369371
std::string GetCodeUnitArrayLiteral(const clang::StringLiteral *expr);
370372
bool IsArrayInitContext() const;
371373

@@ -441,6 +443,10 @@ class Converter : public clang::RecursiveASTVisitor<Converter> {
441443

442444
virtual bool VisitSizeOfPackExpr(clang::SizeOfPackExpr *expr);
443445

446+
virtual bool
447+
VisitConceptSpecializationExpr(clang::ConceptSpecializationExpr *expr);
448+
virtual bool VisitRequiresExpr(clang::RequiresExpr *expr);
449+
444450
virtual bool VisitOffsetOfExpr(clang::OffsetOfExpr *expr);
445451

446452
virtual bool VisitEnumDecl(clang::EnumDecl *decl);

‎cpp2rust/converter/converter_lib.cpp‎

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55

66
#include <clang/AST/DeclTemplate.h>
77
#include <clang/AST/ExprCXX.h>
8+
#include <clang/AST/ExprConcepts.h>
89
#include <clang/AST/Mangle.h>
910
#include <clang/AST/ParentMapContext.h>
1011
#include <clang/Basic/SourceManager.h>
12+
#include <clang/Lex/Lexer.h>
1113
#include <clang/Sema/Initialization.h>
1214
#include <clang/Sema/Sema.h>
1315
#include <llvm/Support/ErrorHandling.h>
@@ -111,6 +113,67 @@ bool IsBuiltinConstantP(const clang::Expr *expr) {
111113
return false;
112114
}
113115

116+
bool IsRustConstEvaluableExpr(const clang::Expr *expr) {
117+
expr = expr->IgnoreParenImpCasts();
118+
if (clang::isa<clang::IntegerLiteral, clang::ConceptSpecializationExpr>(
119+
expr)) {
120+
return true;
121+
}
122+
if (auto *trait = clang::dyn_cast<clang::UnaryExprOrTypeTraitExpr>(expr)) {
123+
return trait->getKind() == clang::UnaryExprOrTypeTrait::UETT_SizeOf;
124+
}
125+
if (auto *unary = clang::dyn_cast<clang::UnaryOperator>(expr)) {
126+
return unary->getOpcode() == clang::UO_LNot &&
127+
IsRustConstEvaluableExpr(unary->getSubExpr());
128+
}
129+
if (auto *binary = clang::dyn_cast<clang::BinaryOperator>(expr)) {
130+
return (binary->isEqualityOp() || binary->isRelationalOp()) &&
131+
IsRustConstEvaluableExpr(binary->getLHS()) &&
132+
IsRustConstEvaluableExpr(binary->getRHS());
133+
}
134+
return false;
135+
}
136+
137+
std::string GetEscapedCharLiteral(char character) {
138+
switch (character) {
139+
case '"':
140+
return "\\\"";
141+
case '\'':
142+
return "\\'";
143+
case '\\':
144+
return "\\\\";
145+
case '\n':
146+
return "\\n";
147+
case '\r':
148+
return "\\r";
149+
case '\t':
150+
return "\\t";
151+
case '\0':
152+
return "\\0";
153+
}
154+
auto uc = static_cast<unsigned char>(character);
155+
if (uc < 0x20 || uc >= 0x7F) {
156+
return std::format("\\x{:02x}", uc);
157+
}
158+
return std::string(1, character);
159+
}
160+
161+
std::string GetAssertMessageAsString(const clang::Expr *expr,
162+
const clang::ASTContext &ctx) {
163+
auto text = clang::Lexer::getSourceText(
164+
clang::CharSourceRange::getTokenRange(expr->getSourceRange()),
165+
ctx.getSourceManager(), ctx.getLangOpts());
166+
std::string message = R"(, ")";
167+
for (char c : text) {
168+
// Doubled so that assert! does not read them as a format placeholder.
169+
if (c == '{' || c == '}') {
170+
message += c;
171+
}
172+
message += GetEscapedCharLiteral(c);
173+
}
174+
return message + '"';
175+
}
176+
114177
bool IsComparisonWithNullOp(const clang::BinaryOperator *expr) {
115178
if (!expr->isComparisonOp()) {
116179
return false;

‎cpp2rust/converter/converter_lib.h‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@ bool IsGlobalVar(const clang::VarDecl *decl);
4242

4343
bool IsGlobalVar(const clang::Expr *expr);
4444

45+
bool IsRustConstEvaluableExpr(const clang::Expr *expr);
46+
47+
std::string GetEscapedCharLiteral(char character);
48+
49+
std::string GetAssertMessageAsString(const clang::Expr *expr,
50+
const clang::ASTContext &ctx);
51+
4552
bool IsComparisonWithNullOp(const clang::BinaryOperator *expr);
4653

4754
bool IsInMainFile(const clang::Decl *decl);

‎tests/unit/concepts.cpp‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// ADDITIONAL_COMPILE_FLAGS: -std=c++20
2+
#include <cassert>
3+
#include <concepts>
4+
5+
template <typename T>
6+
concept Small = sizeof(T) <= 4;
7+
8+
static_assert(Small<int>);
9+
static_assert(sizeof(int) == 4);
10+
11+
template <typename T>
12+
concept HasSize = requires(T t) {
13+
{ t.size() } -> std::same_as<int>;
14+
};
15+
16+
struct Sized {
17+
int size() { return 4; }
18+
};
19+
20+
template <typename T> bool is_small() { return Small<T>; }
21+
22+
template <typename T> bool has_size() {
23+
return requires(T t) { t.size(); };
24+
}
25+
26+
template <typename T> int pick(T x) {
27+
if (std::integral<T> && Small<T>) {
28+
return 1;
29+
}
30+
return 2;
31+
}
32+
33+
int main() {
34+
static_assert(!Small<long>);
35+
assert(is_small<char>());
36+
assert(!is_small<double>());
37+
assert(HasSize<Sized>);
38+
assert(!HasSize<int>);
39+
assert(has_size<Sized>());
40+
assert(!has_size<int>());
41+
assert(pick(1) == 1);
42+
assert(pick(1L) == 2);
43+
assert(pick(1.0f) == 2);
44+
return 0;
45+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
extern crate libcc2rs;
2+
use libcc2rs::*;
3+
use std::cell::RefCell;
4+
use std::collections::BTreeMap;
5+
use std::io::prelude::*;
6+
use std::io::{Read, Seek, Write};
7+
use std::os::fd::AsFd;
8+
use std::rc::{Rc, Weak};
9+
const _: () = assert!(true, "Small<int>");
10+
const _: () = assert!(
11+
(::std::mem::size_of::<i32>() == 4_usize),
12+
"sizeof(int) == 4"
13+
);
14+
#[derive(Clone, ByteRepr, Default)]
15+
pub struct Sized {}
16+
pub fn is_small_0() -> bool {
17+
return true;
18+
}
19+
pub fn is_small_1() -> bool {
20+
return false;
21+
}
22+
pub fn has_size_2() -> bool {
23+
return true;
24+
}
25+
pub fn has_size_3() -> bool {
26+
return false;
27+
}
28+
pub fn pick_4(x: i32) -> i32 {
29+
let x: Value<i32> = Rc::new(RefCell::new(x));
30+
if (true) && (true) {
31+
return 1;
32+
}
33+
return 2;
34+
}
35+
pub fn pick_5(x: i64) -> i32 {
36+
let x: Value<i64> = Rc::new(RefCell::new(x));
37+
if (true) && (false) {
38+
return 1;
39+
}
40+
return 2;
41+
}
42+
pub fn pick_6(x: f32) -> i32 {
43+
let x: Value<f32> = Rc::new(RefCell::new(x));
44+
if (false) && (true) {
45+
return 1;
46+
}
47+
return 2;
48+
}
49+
pub fn main() {
50+
__cpp2rust_init_globals();
51+
std::process::exit(main_0());
52+
}
53+
fn main_0() -> i32 {
54+
const _: () = assert!(!(false), "!Small<long>");;
55+
assert!(({ is_small_0() }));
56+
assert!(!({ is_small_1() }));
57+
assert!(true);
58+
assert!(!(false));
59+
assert!(({ has_size_2() }));
60+
assert!(!({ has_size_3() }));
61+
assert!((({ pick_4(1,) }) == 1));
62+
assert!((({ pick_5(1_i64,) }) == 2));
63+
assert!((({ pick_6(1.0E+0,) }) == 2));
64+
return 0;
65+
}
66+
pub trait SizedImpl {
67+
fn size(&self) -> i32;
68+
}
69+
impl SizedImpl for Ptr<Sized> {
70+
fn size(&self) -> i32 {
71+
return 4;
72+
}
73+
}
74+
pub fn __cpp2rust_init_globals() {}

‎tests/unit/out/unsafe/concepts.rs‎

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
extern crate libc;
2+
use libc::*;
3+
extern crate libcc2rs;
4+
use libcc2rs::*;
5+
use std::collections::BTreeMap;
6+
use std::io::{Read, Seek, Write};
7+
use std::os::fd::{AsFd, FromRawFd, IntoRawFd};
8+
use std::rc::Rc;
9+
const _: () = assert!(true, "Small<int>");
10+
const _: () = assert!(
11+
((::std::mem::size_of::<i32>()) == (4_usize)),
12+
"sizeof(int) == 4"
13+
);
14+
#[repr(C)]
15+
#[derive(Copy, Clone, Default)]
16+
pub struct Sized {}
17+
impl Sized {
18+
pub unsafe fn size(&mut self) -> i32 {
19+
return 4;
20+
}
21+
}
22+
pub unsafe fn is_small_0() -> bool {
23+
return true;
24+
}
25+
pub unsafe fn is_small_1() -> bool {
26+
return false;
27+
}
28+
pub unsafe fn has_size_2() -> bool {
29+
return true;
30+
}
31+
pub unsafe fn has_size_3() -> bool {
32+
return false;
33+
}
34+
pub unsafe fn pick_4(mut x: i32) -> i32 {
35+
if (true) && (true) {
36+
return 1;
37+
}
38+
return 2;
39+
}
40+
pub unsafe fn pick_5(mut x: i64) -> i32 {
41+
if (true) && (false) {
42+
return 1;
43+
}
44+
return 2;
45+
}
46+
pub unsafe fn pick_6(mut x: f32) -> i32 {
47+
if (false) && (true) {
48+
return 1;
49+
}
50+
return 2;
51+
}
52+
pub fn main() {
53+
unsafe {
54+
__cpp2rust_init_globals();
55+
std::process::exit(main_0() as i32);
56+
}
57+
}
58+
unsafe fn main_0() -> i32 {
59+
const _: () = assert!(!(false), "!Small<long>");;
60+
assert!((unsafe { is_small_0() }));
61+
assert!(!(unsafe { is_small_1() }));
62+
assert!(true);
63+
assert!(!(false));
64+
assert!((unsafe { has_size_2() }));
65+
assert!(!(unsafe { has_size_3() }));
66+
assert!(((unsafe { pick_4(1,) }) == (1)));
67+
assert!(((unsafe { pick_5(1_i64,) }) == (2)));
68+
assert!(((unsafe { pick_6(1.0E+0,) }) == (2)));
69+
return 0;
70+
}
71+
pub unsafe fn __cpp2rust_init_globals() {}

0 commit comments

Comments
 (0)