@@ -7,9 +7,31 @@ use rustc_macros::{Decodable, Encodable};
77use rustc_session:: parse:: ParseSess ;
88use rustc_span:: { Ident , Span , Symbol } ;
99
10+ use crate :: errors;
11+
1012pub ( crate ) const RAW_IDENT_ERR : & str = "`${concat(..)}` currently does not support raw identifiers" ;
1113pub ( crate ) const UNSUPPORTED_CONCAT_ELEM_ERR : & str = "expected identifier or string literal" ;
1214
15+ /// Argument specification for a metavariable expression
16+ #[ derive( Clone , Copy ) ]
17+ enum ArgSpec {
18+ /// Any number of args
19+ Any ,
20+ /// Between n and m args (inclusive)
21+ Between ( usize , usize ) ,
22+ /// Exactly n args
23+ Exact ( usize ) ,
24+ }
25+
26+ /// Map of `(name, max_arg_count, variable_count)`.
27+ const EXPR_NAME_ARG_MAP : & [ ( & str , ArgSpec ) ] = & [
28+ ( "concat" , ArgSpec :: Any ) ,
29+ ( "count" , ArgSpec :: Between ( 1 , 2 ) ) ,
30+ ( "ignore" , ArgSpec :: Exact ( 1 ) ) ,
31+ ( "index" , ArgSpec :: Between ( 0 , 1 ) ) ,
32+ ( "len" , ArgSpec :: Between ( 0 , 1 ) ) ,
33+ ] ;
34+
1335/// A meta-variable expression, for expansions based on properties of meta-variables.
1436#[ derive( Debug , PartialEq , Encodable , Decodable ) ]
1537pub ( crate ) enum MetaVarExpr {
@@ -40,11 +62,32 @@ impl MetaVarExpr {
4062 ) -> PResult < ' psess , MetaVarExpr > {
4163 let mut iter = input. iter ( ) ;
4264 let ident = parse_ident ( & mut iter, psess, outer_span) ?;
43- let Some ( TokenTree :: Delimited ( .., Delimiter :: Parenthesis , args) ) = iter. next ( ) else {
44- let msg = "meta-variable expression parameter must be wrapped in parentheses" ;
45- return Err ( psess. dcx ( ) . struct_span_err ( ident. span , msg) ) ;
65+ let next = iter. next ( ) ;
66+ let Some ( TokenTree :: Delimited ( .., Delimiter :: Parenthesis , args) ) = next else {
67+ // No `()`; wrong or no delimiters. Point at a problematic span or a place to
68+ // add parens if it makes sense.
69+ let ( unexpected_span, insert_span) = match next {
70+ Some ( TokenTree :: Delimited ( ..) ) => ( None , None ) ,
71+ Some ( tt) => ( Some ( tt. span ( ) ) , None ) ,
72+ None => ( None , Some ( ident. span . shrink_to_hi ( ) ) ) ,
73+ } ;
74+ let err =
75+ errors:: MveMissingParen { ident_span : ident. span , unexpected_span, insert_span } ;
76+ return Err ( psess. dcx ( ) . create_err ( err) ) ;
4677 } ;
47- check_trailing_token ( & mut iter, psess) ?;
78+
79+ // Ensure there are no trailing tokens in the braces, e.g. `${foo() extra}`
80+ if iter. peek ( ) . is_some ( ) {
81+ let span = iter_span ( & iter) . expect ( "checked is_some above" ) ;
82+ let err = errors:: MveExtraTokens {
83+ span,
84+ ident_span : ident. span ,
85+ extra_count : iter. count ( ) ,
86+ ..Default :: default ( )
87+ } ;
88+ return Err ( psess. dcx ( ) . create_err ( err) ) ;
89+ }
90+
4891 let mut iter = args. iter ( ) ;
4992 let rslt = match ident. as_str ( ) {
5093 "concat" => parse_concat ( & mut iter, psess, outer_span, ident. span ) ?,
@@ -67,7 +110,7 @@ impl MetaVarExpr {
67110 return Err ( err) ;
68111 }
69112 } ;
70- check_trailing_token ( & mut iter, psess) ?;
113+ check_trailing_tokens ( & mut iter, psess, ident ) ?;
71114 Ok ( rslt)
72115 }
73116
@@ -87,20 +130,51 @@ impl MetaVarExpr {
87130 }
88131}
89132
90- // Checks if there are any remaining tokens. For example, `${ignore(ident ... a b c ...)}`
91- fn check_trailing_token < ' psess > (
133+ /// Checks if there are any remaining tokens (for example, `${ignore($valid, extra)}`) and create
134+ /// a diag with the correct arg count if so.
135+ fn check_trailing_tokens < ' psess > (
92136 iter : & mut TokenStreamIter < ' _ > ,
93137 psess : & ' psess ParseSess ,
138+ ident : Ident ,
94139) -> PResult < ' psess , ( ) > {
95- if let Some ( tt) = iter. next ( ) {
96- let mut diag = psess
97- . dcx ( )
98- . struct_span_err ( tt. span ( ) , format ! ( "unexpected token: {}" , pprust:: tt_to_string( tt) ) ) ;
99- diag. span_note ( tt. span ( ) , "meta-variable expression must not have trailing tokens" ) ;
100- Err ( diag)
101- } else {
102- Ok ( ( ) )
140+ if iter. peek ( ) . is_none ( ) {
141+ // All tokens consumed, as expected
142+ return Ok ( ( ) ) ;
103143 }
144+
145+ let ( name, spec) = EXPR_NAME_ARG_MAP
146+ . iter ( )
147+ . find ( |( name, _) | * name == ident. as_str ( ) )
148+ . expect ( "called with an invalid name" ) ;
149+
150+ let ( min_or_exact_args, max_args) = match * spec {
151+ // For expressions like `concat`, all tokens should be consumed already
152+ ArgSpec :: Any => panic ! ( "{name} takes unlimited tokens but didn't eat them all" ) ,
153+ ArgSpec :: Between ( min, max) => ( min, Some ( max) ) ,
154+ ArgSpec :: Exact ( n) => ( n, None ) ,
155+ } ;
156+
157+ let err = errors:: MveExtraTokens {
158+ span : iter_span ( iter) . expect ( "checked is_none above" ) ,
159+ ident_span : ident. span ,
160+ extra_count : iter. count ( ) ,
161+
162+ exact_args_note : if max_args. is_some ( ) { None } else { Some ( ( ) ) } ,
163+ range_args_note : if max_args. is_some ( ) { Some ( ( ) ) } else { None } ,
164+ min_or_exact_args,
165+ max_args : max_args. unwrap_or_default ( ) ,
166+ name,
167+ } ;
168+ Err ( psess. dcx ( ) . create_err ( err) )
169+ }
170+
171+ /// Returns a span encompassing all tokens in the iterator if there is at least one item.
172+ fn iter_span ( iter : & TokenStreamIter < ' _ > ) -> Option < Span > {
173+ let mut iter = iter. clone ( ) ; // cloning is cheap
174+ let first_sp = iter. next ( ) ?. span ( ) ;
175+ let last_sp = iter. last ( ) . map ( TokenTree :: span) . unwrap_or ( first_sp) ;
176+ let span = first_sp. with_hi ( last_sp. hi ( ) ) ;
177+ Some ( span)
104178}
105179
106180/// Indicates what is placed in a `concat` parameter. For example, literals
0 commit comments