| 
1 | 1 | use super::prelude::*;  | 
2 |  | -use crate::session_diagnostics::{FeatureExpectedSingleWord, LimitInvalid};  | 
 | 2 | +use crate::session_diagnostics::{ExpectedSingleWord, LimitInvalid};  | 
3 | 3 | 
 
  | 
4 | 4 | pub(crate) struct CrateNameParser;  | 
5 | 5 | 
 
  | 
@@ -186,7 +186,64 @@ impl<S: Stage> CombineAttributeParser<S> for FeatureParser {  | 
186 | 186 |             let path = elem.path();  | 
187 | 187 |             let Some(ident) = path.word() else {  | 
188 | 188 |                 let first_segment = elem.path().segments().next().expect("at least one segment");  | 
189 |  | -                cx.emit_err(FeatureExpectedSingleWord {  | 
 | 189 | +                cx.emit_err(ExpectedSingleWord {  | 
 | 190 | +                    description: "rust features",  | 
 | 191 | +                    span: path.span(),  | 
 | 192 | +                    first_segment_span: first_segment.span,  | 
 | 193 | +                    first_segment: first_segment.name,  | 
 | 194 | +                });  | 
 | 195 | +                continue;  | 
 | 196 | +            };  | 
 | 197 | + | 
 | 198 | +            res.push(ident);  | 
 | 199 | +        }  | 
 | 200 | + | 
 | 201 | +        res  | 
 | 202 | +    }  | 
 | 203 | +}  | 
 | 204 | + | 
 | 205 | +pub(crate) struct RegisterToolParser;  | 
 | 206 | + | 
 | 207 | +impl<S: Stage> CombineAttributeParser<S> for RegisterToolParser {  | 
 | 208 | +    const PATH: &[Symbol] = &[sym::register_tool];  | 
 | 209 | +    type Item = Ident;  | 
 | 210 | +    const CONVERT: ConvertFn<Self::Item> = AttributeKind::RegisterTool;  | 
 | 211 | + | 
 | 212 | +    // FIXME: recursion limit is allowed on all targets and ignored,  | 
 | 213 | +    //        even though it should only be valid on crates of course  | 
 | 214 | +    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS);  | 
 | 215 | +    const TEMPLATE: AttributeTemplate = template!(List: &["tool1, tool2, ..."]);  | 
 | 216 | + | 
 | 217 | +    fn extend<'c>(  | 
 | 218 | +        cx: &'c mut AcceptContext<'_, '_, S>,  | 
 | 219 | +        args: &'c ArgParser<'_>,  | 
 | 220 | +    ) -> impl IntoIterator<Item = Self::Item> + 'c {  | 
 | 221 | +        let ArgParser::List(list) = args else {  | 
 | 222 | +            cx.expected_list(cx.attr_span);  | 
 | 223 | +            return Vec::new();  | 
 | 224 | +        };  | 
 | 225 | + | 
 | 226 | +        if list.is_empty() {  | 
 | 227 | +            cx.warn_empty_attribute(cx.attr_span);  | 
 | 228 | +        }  | 
 | 229 | + | 
 | 230 | +        let mut res = Vec::new();  | 
 | 231 | + | 
 | 232 | +        for elem in list.mixed() {  | 
 | 233 | +            let Some(elem) = elem.meta_item() else {  | 
 | 234 | +                cx.expected_identifier(elem.span());  | 
 | 235 | +                continue;  | 
 | 236 | +            };  | 
 | 237 | +            if let Err(arg_span) = elem.args().no_args() {  | 
 | 238 | +                cx.expected_no_args(arg_span);  | 
 | 239 | +                continue;  | 
 | 240 | +            }  | 
 | 241 | + | 
 | 242 | +            let path = elem.path();  | 
 | 243 | +            let Some(ident) = path.word() else {  | 
 | 244 | +                let first_segment = elem.path().segments().next().expect("at least one segment");  | 
 | 245 | +                cx.emit_err(ExpectedSingleWord {  | 
 | 246 | +                    description: "tools",  | 
190 | 247 |                     span: path.span(),  | 
191 | 248 |                     first_segment_span: first_segment.span,  | 
192 | 249 |                     first_segment: first_segment.name,  | 
 | 
0 commit comments