Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cppshift"
version = "0.1.1"
version = "0.1.2"
authors = ["Jérémy HERGAULT", "Enzo PASQUALINI"]
description = "CPP parser and transpiler"
repository = "https://github.com/worldline/cppshift"
Expand All @@ -16,11 +16,11 @@ transpiler = ["ast", "dep:serde", "dep:syn", "dep:quote", "dep:proc-macro2"]
miette = { version = "7", features = ["fancy"] }
thiserror = "2"
serde = { version = "1", features = ["derive"], optional = true }
syn = { version = "2", features = ["full", "extra-traits", "printing"], optional = true }
syn = { version = "3", features = ["full", "extra-traits", "printing"], optional = true }
quote = { version = "1", optional = true }
proc-macro2 = { version = "1", optional = true }

[dev-dependencies]
tokio = { version = "1", features = ["macros"] }
tokio = { version = ">=1.11.1, < 2", features = ["macros"] }
reqwest = "0.13"
glob = "0.3"
111 changes: 106 additions & 5 deletions src/ast/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,28 @@ fn parse_item_enum<'de>(p: &mut Parser<'de>) -> Result<ItemEnum<'de>, AstError>
}
p.expect(TokenKind::RightBrace)?;
}

// A trailing declarator may follow the enum body, e.g.
// `enum { A, B } mField;` declares an unnamed enum type and a member of
// that type in one statement. The declarator (and any initializer) is
// discarded — only the enum type is retained. Skip to the terminating `;`,
// balancing brackets from array declarators or brace/paren initializers.
if p.peek_kind() != Some(TokenKind::Semicolon) {
let mut depth = 0usize;
while let Some(kind) = p.peek_kind() {
match kind {
TokenKind::LeftBrace | TokenKind::LeftParenthese | TokenKind::LeftBracket => {
depth += 1;
}
TokenKind::RightBrace | TokenKind::RightParenthese | TokenKind::RightBracket => {
depth = depth.saturating_sub(1);
}
TokenKind::Semicolon if depth == 0 => break,
_ => {}
}
p.bump()?;
}
}
p.expect(TokenKind::Semicolon)?;

Ok(ItemEnum {
Expand All @@ -662,6 +684,9 @@ fn parse_item_struct<'de>(p: &mut Parser<'de>) -> Result<ItemStruct<'de>, AstErr
None
};

// `struct Foo final : ...` — the `final` specifier follows the name.
p.eat(TokenKind::KeywordFinal);

// Forward declaration: struct Foo;
if p.eat(TokenKind::Semicolon).is_some() {
return Ok(ItemStruct {
Expand Down Expand Up @@ -705,6 +730,9 @@ fn parse_item_class<'de>(p: &mut Parser<'de>) -> Result<ItemClass<'de>, AstError
None
};

// `class Foo final : ...` — the `final` specifier follows the name.
p.eat(TokenKind::KeywordFinal);

// Forward declaration: class Foo;
if p.eat(TokenKind::Semicolon).is_some() {
return Ok(ItemClass {
Expand Down Expand Up @@ -2154,11 +2182,26 @@ fn parse_fields_named<'de>(
}
Some(TokenKind::KeywordFriend) => {
p.bump()?;
let inner = parse_item(p)?;
members.push(Member::Friend(ItemFriend {
attrs: Vec::new(),
item: Box::new(inner),
}));
// Friend declarations are not emitted, and can carry constructs
// the item parser doesn't handle (e.g. a templated befriended
// class `friend class cFoo<A, B>;`). Skip to the terminating
// `;`, balancing any `{}`/`()` from an inline friend definition.
let mut brace_depth = 0usize;
let mut paren_depth = 0usize;
while let Some(kind) = p.peek_kind() {
match kind {
TokenKind::LeftBrace => brace_depth += 1,
TokenKind::RightBrace => brace_depth = brace_depth.saturating_sub(1),
TokenKind::LeftParenthese => paren_depth += 1,
TokenKind::RightParenthese => paren_depth = paren_depth.saturating_sub(1),
TokenKind::Semicolon if brace_depth == 0 && paren_depth == 0 => {
p.bump()?;
break;
}
_ => {}
}
p.bump()?;
}
continue;
}
_ => {}
Expand Down Expand Up @@ -3337,6 +3380,18 @@ fn parse_expr_precedence<'de>(
Some(TokenKind::Dot) => {
p.bump()?;
let member = parse_ident(p)?;
// Explicit template arguments on a method call, e.g.
// `obj.method<T>(...)`. The turbofish is discarded (method calls
// don't retain template args); only recognized when it parses as
// angle-bracketed args and is immediately followed by `(`, so a
// genuine comparison like `a.b < c` is left untouched.
if p.peek_kind() == Some(TokenKind::LeftChevron) {
let cp = p.checkpoint();
match parse_angle_bracketed_args(p) {
Ok(_) if p.peek_kind() == Some(TokenKind::LeftParenthese) => {}
_ => p.restore(&cp),
}
}
if p.peek_kind() == Some(TokenKind::LeftParenthese) {
p.bump()?;
let mut args = Punctuated::new();
Expand Down Expand Up @@ -3368,6 +3423,18 @@ fn parse_expr_precedence<'de>(
Some(TokenKind::PointerMember) => {
p.bump()?;
let member = parse_ident(p)?;
// Explicit template arguments on a method call, e.g.
// `ptr->method<T>(...)`. The turbofish is discarded (method calls
// don't retain template args); only recognized when it parses as
// angle-bracketed args and is immediately followed by `(`, so a
// genuine comparison like `a->b < c` is left untouched.
if p.peek_kind() == Some(TokenKind::LeftChevron) {
let cp = p.checkpoint();
match parse_angle_bracketed_args(p) {
Ok(_) if p.peek_kind() == Some(TokenKind::LeftParenthese) => {}
_ => p.restore(&cp),
}
}
if p.peek_kind() == Some(TokenKind::LeftParenthese) {
p.bump()?;
let mut args = Punctuated::new();
Expand Down Expand Up @@ -4336,6 +4403,40 @@ mod tests {
}
}

#[test]
fn parse_enum_with_trailing_declarator() {
// Anonymous enum declaring a member in one statement (C++ allows this).
let file = parse("enum { FC_NONE, FC_ALL } mFuncCallHistoryType;");
match &file.items[0] {
Item::Enum(e) => {
assert!(e.ident.is_none());
assert_eq!(e.variants.len(), 2);
}
other => panic!("expected Enum, got {other:?}"),
}
}

#[test]
fn parse_method_call_with_template_args() {
// Explicit template arguments on a method call (turbofish); the args are
// discarded but the call must parse as a method call.
let file = parse("void f() { pTxn->searchDecoW<cTDPci>(); }");
match &file.items[0] {
Item::Fn(_) => {}
other => panic!("expected Fn, got {other:?}"),
}
}

#[test]
fn parse_arrow_comparison_still_works() {
// A genuine comparison after `->` must not be mistaken for a turbofish.
let file = parse("void f() { bool b = a->x < c; }");
match &file.items[0] {
Item::Fn(_) => {}
other => panic!("expected Fn, got {other:?}"),
}
}

#[test]
fn parse_struct_forward() {
let file = parse("struct Foo;");
Expand Down
20 changes: 18 additions & 2 deletions src/transpile/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use quote::ToTokens;
use crate::SourceCodeSpan as _;
use crate::ast::expr::{
BinaryOp, Expr, ExprBinary, ExprBool, ExprIndex, ExprMethodCall, ExprNullptr, ExprParen,
ExprUnary, UnaryOp,
ExprUnary, LitKind, UnaryOp,
};
use crate::transpile::{Transpile, TranspileContext, Transpiler};

Expand Down Expand Up @@ -90,7 +90,23 @@ impl<'de> Transpile for Expr<'de> {
) -> Result<(), TranspileError> {
match self {
Expr::Lit(lit) => {
let rust_expr: syn::Expr = syn::parse_str(lit.span.src()).map_err(|e| {
// C++ integer literals may carry width/sign suffixes (`LL`, `ULL`,
// `UL`, `L`, `U`) that are not valid Rust; strip them so the
// literal parses. Only applied to integer literals, so string and
// char literals (which may legitimately contain those letters) are
// untouched.
let src = lit.span.src();
let normalized = if lit.kind == LitKind::Integer {
src.trim_end_matches(['l', 'L', 'u', 'U'])
} else {
src
};
let normalized = if normalized.is_empty() {
src
} else {
normalized
};
let rust_expr: syn::Expr = syn::parse_str(normalized).map_err(|e| {
unsupported_from_expr(&format!("cannot parse literal: {e}"), self)
})?;
tokens.extend(quote::quote!(#rust_expr));
Expand Down
6 changes: 3 additions & 3 deletions src/transpile/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ mod tests {
} else {
panic!(
"expected field member[0], got {:?}",
&named_fields.members[0]
named_fields.members[0]
);
}

Expand All @@ -394,7 +394,7 @@ mod tests {
} else {
panic!(
"expected field member[3], got {:?}",
&named_fields.members[3]
named_fields.members[3]
);
}

Expand All @@ -409,7 +409,7 @@ mod tests {
} else {
panic!(
"expected field member[5], got {:?}",
&named_fields.members[5]
named_fields.members[5]
);
}
}
Expand Down
Loading