Skip to content
Open
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
236 changes: 229 additions & 7 deletions src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3742,6 +3742,20 @@ impl Parse for TypeHandle {
return Ok((handle, tail));
}

// C++20 placeholder type template-parameter declaration. This appears
// in contexts such as generic lambda signatures; parse it as `auto`
// so the stream remains well-formed.
//
// Like other template-parameter declaration qualifiers, this is
// treated as mangling syntax support and not represented as a distinct
// renderable AST construct.
if let Ok(tail) = consume(b"Ty", input) {
return Ok((
TypeHandle::Builtin(BuiltinType::Standard(StandardBuiltinType::Auto)),
tail,
));
}

// ::= <qualified-type>
// We don't have a separate type for the <qualified-type> production.
// Process these all up front, so that any ambiguity that might exist
Expand Down Expand Up @@ -5747,6 +5761,87 @@ impl Parse for TemplateArg {
) -> Result<(TemplateArg, IndexStr<'b>)> {
try_begin_parse!("TemplateArg", ctx, input);

// Parse C++20 template-parameter declarations that can prefix an
// actual template argument, such as:
// - `Tn <type>`
// - `Tk <name>`
// - `Tt <template-param-decl>+ E`
// - `Tp <template-param-decl>`
// - `Ty`
//
// We parse these qualifiers structurally for stream correctness, then
// continue with the following actual `<template-arg>`.
//
// This helper consumes qualifier prefixes without creating additional
// wrapper AST nodes for those prefixes in this path.
fn parse_template_param_decl<'a, 'b>(
ctx: &'a ParseContext,
subs: &'a mut SubstitutionTable,
input: IndexStr<'b>,
) -> Result<IndexStr<'b>> {
// Only classify as UnexpectedEnd for genuinely truncated qualifier
// prefixes: empty input, or a lone leading `T`.
let saw_truncated_prefix =
input.is_empty() || (input.peek() == Some(b'T') && input.len() == 1);

match consume(b"Ty", input) {
Ok(tail) => return Ok(tail),
Err(_) => {}
}

match consume(b"Tn", input) {
Ok(tail) => {
let (_, tail) = TypeHandle::parse(ctx, subs, tail)?;
return Ok(tail);
}
Err(_) => {}
}

match consume(b"Tk", input) {
Ok(tail) => {
let (_, tail) = Name::parse(ctx, subs, tail)?;
return Ok(tail);
}
Err(_) => {}
}

match TemplateParamDecl::parse(ctx, subs, input) {
Ok((_param_decl, tail)) => return Ok(tail),
Err(_) => {}
}

match consume(b"Tp", input) {
Ok(tail) => return parse_template_param_decl(ctx, subs, tail),
Err(_) => {}
}

match consume(b"Tt", input) {
Ok(tail) => return parse_tt_param_decl_list(ctx, subs, tail),
Err(_) => {}
}

if saw_truncated_prefix {
Err(error::Error::UnexpectedEnd)
} else {
Err(error::Error::UnexpectedText)
}
}

fn parse_tt_param_decl_list<'a, 'b>(
ctx: &'a ParseContext,
subs: &'a mut SubstitutionTable,
input: IndexStr<'b>,
) -> Result<IndexStr<'b>> {
// `Tt` requires one-or-more `<template-param-decl>` entries.
let mut tail = parse_template_param_decl(ctx, subs, input)?;
loop {
if let Ok(next) = consume(b"E", tail) {
return Ok(next);
}
tail = parse_template_param_decl(ctx, subs, tail)?;
}
}

if let Ok(tail) = consume(b"X", input) {
let (expr, tail) = Expression::parse(ctx, subs, tail)?;
let tail = consume(b"E", tail)?;
Expand All @@ -5757,8 +5852,17 @@ impl Parse for TemplateArg {
return Ok((TemplateArg::SimpleExpression(expr), tail));
}

if let Ok((ty, tail)) = try_recurse!(TypeHandle::parse(ctx, subs, input)) {
return Ok((TemplateArg::Type(ty), tail));
// C++20: a template-arg can also be prefixed by a constrained type
// template-parameter declaration (`Tk <name>`). Parse this qualifier
// structurally and continue with the following actual template-arg.
if let Ok(tail) = consume(b"Tk", input) {
let (_, tail) = Name::parse(ctx, subs, tail)?;
return TemplateArg::parse(ctx, subs, tail);
}

if let Ok(tail) = consume(b"Tt", input) {
let tail = parse_tt_param_decl_list(ctx, subs, tail)?;
return TemplateArg::parse(ctx, subs, tail);
}

if let Ok((template_param_decl, tail)) =
Expand All @@ -5771,6 +5875,19 @@ impl Parse for TemplateArg {
));
}

if let Ok(tail) = consume(b"Tp", input) {
let tail = parse_template_param_decl(ctx, subs, tail)?;
return TemplateArg::parse(ctx, subs, tail);
}

if let Ok(tail) = consume(b"Ty", input) {
return TemplateArg::parse(ctx, subs, tail);
}

if let Ok((ty, tail)) = try_recurse!(TypeHandle::parse(ctx, subs, input)) {
return Ok((TemplateArg::Type(ty), tail));
}

let tail = if input.peek() == Some(b'J') {
consume(b"J", input)?
} else {
Expand Down Expand Up @@ -9689,6 +9806,13 @@ mod tests {
AbiTags::default()))))))
]
}
b"Ty..." => {
TypeHandle::Builtin(
BuiltinType::Standard(StandardBuiltinType::Auto)
),
b"...",
[]
}
}
Err => {
b"P" => Error::UnexpectedEnd,
Expand Down Expand Up @@ -10156,6 +10280,66 @@ mod tests {
});
}

#[test]
fn parse_realworld_tfunctionrefcaller_full_mangled_name_probe() {
let mut subs = SubstitutionTable::new();
let ctx = ParseContext::new(Default::default());
let input = IndexStr::new(
b"_ZN2UE4Core7Private8Function18TFunctionRefCallerIZN26IEditorDataStorageProvider9AddColumnITkNS_6Editor11DataStorage15TDataColumnTypeE24FTypedElementLabelColumnEEvyOT_EUlPvRK13UScriptStructE_vJSB_SE_EE4CallESB_RSB_SE_",
);

match MangledName::parse(&ctx, &mut subs, input) {
Ok((_name, tail)) => assert!(
tail.is_empty(),
"tfunctionrefcaller full parse left tail: {:?}",
String::from_utf8_lossy(tail.as_ref())
),
Err(err) => panic!("failed tfunctionrefcaller full mangled name: {:?}", err),
}
}

#[test]
fn parse_realworld_fmesh_deletevertexinstance_full_mangled_name_probe() {
let mut subs = SubstitutionTable::new();
let ctx = ParseContext::new(Default::default());
let input = IndexStr::new(
b"_ZN16FMeshDescription29DeleteVertexInstance_InternalITtTpTyE6TArrayEEv17FVertexInstanceIDPT_IJ9FVertexIDEE",
);

match MangledName::parse(&ctx, &mut subs, input) {
Ok((_name, tail)) => assert!(
tail.is_empty(),
"fmesh deletevertexinstance full parse left tail: {:?}",
String::from_utf8_lossy(tail.as_ref())
),
Err(err) => panic!(
"failed fmesh deletevertexinstance full mangled name: {:?}",
err
),
}
}

#[test]
fn parse_realworld_pcg_callbackwithrighttype_full_mangled_name_probe() {
let mut subs = SubstitutionTable::new();
let ctx = ParseContext::new(Default::default());
let input = IndexStr::new(
b"_ZN20PCGMetadataAttribute21CallbackWithRightTypeIZN18PCGPropertyHelpers35ExtractAttributeSetAsArrayOfStructsI26FPCGSelectGrammarCriterionEE6TArrayIT_22TSizedDefaultAllocatorILi32EEEPK13UPCGParamDataPK4TMapI5FName6TTupleIJSD_bEE20FDefaultSetAllocator27TDefaultMapHashableKeyFuncsISD_SF_Lb0EEEP11FPCGContextEUlTyS5_E_JEEEDctS5_DpOT0_",
);

match MangledName::parse(&ctx, &mut subs, input) {
Ok((_name, tail)) => assert!(
tail.is_empty(),
"pcg callbackwithrighttype full parse left tail: {:?}",
String::from_utf8_lossy(tail.as_ref())
),
Err(err) => panic!(
"failed pcg callbackwithrighttype full mangled name: {:?}",
err
),
}
}

#[test]
fn parse_template_arg() {
assert_parse!(TemplateArg {
Expand Down Expand Up @@ -10272,13 +10456,15 @@ mod tests {
Err => {
b"..." => Error::UnexpectedText,
b"X..." => Error::UnexpectedText,
b"TtES_..." => Error::UnexpectedText,
b"J..." => Error::UnexpectedText,
b"JS_..." => Error::UnexpectedText,
// <template-param-decl>s that we don't implement yet.
b"Ty" => Error::UnexpectedText,
b"Tk" => Error::UnexpectedText,
b"Tt" => Error::UnexpectedText,
b"Tp" => Error::UnexpectedText,
// Qualifier prefixes are parsed structurally, but still
// require a following <template-arg> to succeed.
b"Ty" => Error::UnexpectedEnd,
b"Tk" => Error::UnexpectedEnd,
b"Tt" => Error::UnexpectedEnd,
b"Tp" => Error::UnexpectedEnd,
b"JS_" => Error::UnexpectedEnd,
b"X" => Error::UnexpectedEnd,
b"J" => Error::UnexpectedEnd,
Expand All @@ -10288,6 +10474,42 @@ mod tests {
});
}

#[test]
fn parse_template_arg_cxx20_qualifier_prefix_forms() {
let mut subs = SubstitutionTable::new();
let ctx = ParseContext::new(Default::default());

let input = IndexStr::new(b"Tk1ac...");
let (arg, tail) = TemplateArg::parse(&ctx, &mut subs, input).unwrap();
assert_eq!(
arg,
TemplateArg::Type(TypeHandle::Builtin(BuiltinType::Standard(
StandardBuiltinType::Char,
)))
);
assert_eq!(tail.as_ref(), b"...");

let input = IndexStr::new(b"TtTncEc...");
let (arg, tail) = TemplateArg::parse(&ctx, &mut subs, input).unwrap();
assert_eq!(
arg,
TemplateArg::Type(TypeHandle::Builtin(BuiltinType::Standard(
StandardBuiltinType::Char,
)))
);
assert_eq!(tail.as_ref(), b"...");

let input = IndexStr::new(b"TpTyc...");
let (arg, tail) = TemplateArg::parse(&ctx, &mut subs, input).unwrap();
assert_eq!(
arg,
TemplateArg::Type(TypeHandle::Builtin(BuiltinType::Standard(
StandardBuiltinType::Char,
)))
);
assert_eq!(tail.as_ref(), b"...");
}

#[test]
fn parse_expression() {
assert_parse!(Expression {
Expand Down
Loading