@@ -7,6 +7,8 @@ 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
@@ -40,11 +42,32 @@ impl MetaVarExpr {
4042 ) -> PResult < ' psess , MetaVarExpr > {
4143 let mut iter = input. iter ( ) ;
4244 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) ) ;
45+ let next = iter. next ( ) ;
46+ let Some ( TokenTree :: Delimited ( .., Delimiter :: Parenthesis , args) ) = next else {
47+ // No `()`; wrong or no delimiters. Point at a problematic span or a place to
48+ // add parens if it makes sense.
49+ let ( unexpected_span, insert_span) = match next {
50+ Some ( TokenTree :: Delimited ( ..) ) => ( None , None ) ,
51+ Some ( tt) => ( Some ( tt. span ( ) ) , None ) ,
52+ None => ( None , Some ( ident. span . shrink_to_hi ( ) ) ) ,
53+ } ;
54+ let err =
55+ errors:: MveMissingParen { ident_span : ident. span , unexpected_span, insert_span } ;
56+ return Err ( psess. dcx ( ) . create_err ( err) ) ;
4657 } ;
47- check_trailing_token ( & mut iter, psess) ?;
58+
59+ // Ensure there are no trailing tokens in the braces, e.g. `${foo() extra}`
60+ if iter. peek ( ) . is_some ( ) {
61+ let span = iter_span ( & iter) . expect ( "checked is_some above" ) ;
62+ let err = errors:: MveExtraTokens {
63+ span,
64+ ident_span : ident. span ,
65+ extra_count : iter. count ( ) ,
66+ ..Default :: default ( )
67+ } ;
68+ return Err ( psess. dcx ( ) . create_err ( err) ) ;
69+ }
70+
4871 let mut iter = args. iter ( ) ;
4972 let rslt = match ident. as_str ( ) {
5073 "concat" => parse_concat ( & mut iter, psess, outer_span, ident. span ) ?,
@@ -67,7 +90,7 @@ impl MetaVarExpr {
6790 return Err ( err) ;
6891 }
6992 } ;
70- check_trailing_token ( & mut iter, psess) ?;
93+ check_trailing_tokens ( & mut iter, psess, ident ) ?;
7194 Ok ( rslt)
7295 }
7396
@@ -87,20 +110,51 @@ impl MetaVarExpr {
87110 }
88111}
89112
90- // Checks if there are any remaining tokens. For example, `${ignore(ident ... a b c ...)}`
91- fn check_trailing_token < ' psess > (
113+ /// Checks if there are any remaining tokens (for example, `${ignore($valid, extra)}`) and create
114+ /// a diag with the correct arg count if so.
115+ fn check_trailing_tokens < ' psess > (
92116 iter : & mut TokenStreamIter < ' _ > ,
93117 psess : & ' psess ParseSess ,
118+ ident : Ident ,
94119) -> 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 ( ( ) )
120+ if iter. peek ( ) . is_none ( ) {
121+ // All tokens consumed, as expected
122+ return Ok ( ( ) ) ;
103123 }
124+
125+ // `None` for max indicates the arg count must be exact, `Some` indicates a range is accepted.
126+ let ( min_or_exact_args, max_args) = match ident. as_str ( ) {
127+ "concat" => panic ! ( "concat takes unlimited tokens but didn't eat them all" ) ,
128+ "ignore" => ( 1 , None ) ,
129+ // 1 or 2 args
130+ "count" => ( 1 , Some ( 2 ) ) ,
131+ // 0 or 1 arg
132+ "index" => ( 0 , Some ( 1 ) ) ,
133+ "len" => ( 0 , Some ( 1 ) ) ,
134+ other => unreachable ! ( "unknown MVEs should be rejected earlier (got `{other}`)" ) ,
135+ } ;
136+
137+ let err = errors:: MveExtraTokens {
138+ span : iter_span ( iter) . expect ( "checked is_none above" ) ,
139+ ident_span : ident. span ,
140+ extra_count : iter. count ( ) ,
141+
142+ exact_args_note : if max_args. is_some ( ) { None } else { Some ( ( ) ) } ,
143+ range_args_note : if max_args. is_some ( ) { Some ( ( ) ) } else { None } ,
144+ min_or_exact_args,
145+ max_args : max_args. unwrap_or_default ( ) ,
146+ name : ident. to_string ( ) ,
147+ } ;
148+ Err ( psess. dcx ( ) . create_err ( err) )
149+ }
150+
151+ /// Returns a span encompassing all tokens in the iterator if there is at least one item.
152+ fn iter_span ( iter : & TokenStreamIter < ' _ > ) -> Option < Span > {
153+ let mut iter = iter. clone ( ) ; // cloning is cheap
154+ let first_sp = iter. next ( ) ?. span ( ) ;
155+ let last_sp = iter. last ( ) . map ( TokenTree :: span) . unwrap_or ( first_sp) ;
156+ let span = first_sp. with_hi ( last_sp. hi ( ) ) ;
157+ Some ( span)
104158}
105159
106160/// Indicates what is placed in a `concat` parameter. For example, literals
0 commit comments