diff --git a/src/ast.rs b/src/ast.rs index c64d698..56201b3 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -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, + )); + } + // ::= // We don't have a separate type for the production. // Process these all up front, so that any ambiguity that might exist @@ -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 ` + // - `Tk ` + // - `Tt + E` + // - `Tp ` + // - `Ty` + // + // We parse these qualifiers structurally for stream correctness, then + // continue with the following actual ``. + // + // 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> { + // 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> { + // `Tt` requires one-or-more `` 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)?; @@ -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 `). 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)) = @@ -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 { @@ -9689,6 +9806,13 @@ mod tests { AbiTags::default())))))) ] } + b"Ty..." => { + TypeHandle::Builtin( + BuiltinType::Standard(StandardBuiltinType::Auto) + ), + b"...", + [] + } } Err => { b"P" => Error::UnexpectedEnd, @@ -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 { @@ -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, - // 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 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, @@ -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 {