@@ -70,13 +70,41 @@ fn parse_node(group: proc_macro2::Group) -> Result<Node> {
7070 let mut fields = Vec :: new ( ) ;
7171 while peek_is_field ( & mut tokens) {
7272 let name = expect_ident ( & mut tokens, "expected field name" ) ?;
73- if content. is_some ( ) {
73+ let is_location = name == "location" ;
74+ if content. is_some ( ) && !is_location {
7475 return Err ( syn:: Error :: new_spanned (
7576 name,
76- "literal nodes cannot have named fields " ,
77+ "literal nodes can only have the special `location` field " ,
7778 ) ) ;
7879 }
7980 expect_punct ( & mut tokens, ':' , "expected `:` after field name" ) ?;
81+ if is_location {
82+ if !peek_is_group ( & mut tokens, Delimiter :: Brace ) {
83+ let error = match tokens. peek ( ) {
84+ Some ( token) => syn:: Error :: new_spanned (
85+ token. clone ( ) ,
86+ "`location` field value must be a Rust block" ,
87+ ) ,
88+ None => syn:: Error :: new (
89+ Span :: call_site ( ) ,
90+ "`location` field value must be a Rust block" ,
91+ ) ,
92+ } ;
93+ return Err ( error) ;
94+ }
95+ let block = parse_block ( expect_group ( & mut tokens, Delimiter :: Brace ) ?) ?;
96+ if let Some ( TokenTree :: Punct ( punctuation) ) = tokens. peek ( ) {
97+ if punctuation. as_char ( ) == '?' {
98+ return Err ( syn:: Error :: new_spanned (
99+ punctuation. clone ( ) ,
100+ "`?` is not valid on a `location` field" ,
101+ ) ) ;
102+ }
103+ }
104+ fields. push ( Field :: Location { block } ) ;
105+ continue ;
106+ }
107+
80108 let value = parse_one ( & mut tokens) ?;
81109 let optional = if matches ! ( value, Template :: Splice ( _) ) {
82110 reject_stray_optional ( & mut tokens, true ) ?;
@@ -240,6 +268,46 @@ mod tests {
240268 assert_eq ! ( node. fields. len( ) , 2 ) ;
241269 }
242270
271+ #[ test]
272+ fn parses_location_fields_on_literal_nodes ( ) {
273+ let template = parse_template ( quote ! {
274+ ( identifier "name" location: { source} )
275+ } )
276+ . unwrap ( ) ;
277+
278+ let Template :: Node ( node) = template else {
279+ panic ! ( "expected node template" ) ;
280+ } ;
281+ assert_eq ! ( node. kind, "identifier" ) ;
282+ assert ! ( node. content. is_some( ) ) ;
283+ assert ! ( matches!( node. fields. as_slice( ) , [ Field :: Location { .. } ] ) ) ;
284+ }
285+
286+ #[ test]
287+ fn rejects_non_block_location_fields ( ) {
288+ let result = parse_template ( quote ! {
289+ ( identifier "name" location: ( identifier "source" ) )
290+ } ) ;
291+ let Err ( error) = result else {
292+ panic ! ( "expected a non-block location value to be rejected" ) ;
293+ } ;
294+ assert_eq ! (
295+ error. to_string( ) ,
296+ "`location` field value must be a Rust block"
297+ ) ;
298+ }
299+
300+ #[ test]
301+ fn rejects_optional_location_fields ( ) {
302+ let result = parse_template ( quote ! {
303+ ( identifier "name" location: { source} ?)
304+ } ) ;
305+ let Err ( error) = result else {
306+ panic ! ( "expected an optional location field to be rejected" ) ;
307+ } ;
308+ assert_eq ! ( error. to_string( ) , "`?` is not valid on a `location` field" ) ;
309+ }
310+
243311 #[ test]
244312 fn parses_lists_and_skips_empty_nodes ( ) {
245313 let templates = parse_templates ( quote ! {
@@ -254,13 +322,16 @@ mod tests {
254322 }
255323
256324 #[ test]
257- fn rejects_fields_on_literal_nodes ( ) {
325+ fn rejects_named_fields_on_literal_nodes ( ) {
258326 let result = parse_template ( quote ! {
259327 ( identifier "name" child: ( identifier "nested" ) )
260328 } ) ;
261329 let Err ( error) = result else {
262330 panic ! ( "expected literal node fields to be rejected" ) ;
263331 } ;
264- assert_eq ! ( error. to_string( ) , "literal nodes cannot have named fields" ) ;
332+ assert_eq ! (
333+ error. to_string( ) ,
334+ "literal nodes can only have the special `location` field"
335+ ) ;
265336 }
266337}
0 commit comments