11use rustc_ast:: ast:: { AttrStyle , LitKind , MetaItemLit } ;
2+ use rustc_data_structures:: fx:: { FxHashSet , FxIndexMap , IndexEntry } ;
23use rustc_errors:: { Applicability , msg} ;
34use rustc_feature:: AttributeStability ;
45use rustc_hir:: Target ;
56use rustc_hir:: attrs:: {
6- AttributeKind , CfgEntry , CfgHideShow , CfgInfo , DocAttribute , DocInline , HideOrShow ,
7+ AttributeKind , CfgEntry , CfgHideShow , DocAttribute , DocCfgHideShow , DocCfgHideShowValue ,
8+ DocInline , HideOrShow ,
79} ;
810use rustc_session:: errors:: feature_err;
911use rustc_span:: { Span , Symbol , edition, sym} ;
10- use thin_vec:: ThinVec ;
1112
1213use super :: prelude:: { ALL_TARGETS , AllowedTargets } ;
1314use super :: { AcceptMapping , AttributeParser , template} ;
1415use crate :: context:: { AcceptContext , FinalizeContext } ;
1516use crate :: diagnostics:: {
1617 AttrCrateLevelOnly , DocAliasDuplicated , DocAutoCfgExpectsHideOrShow ,
17- DocAutoCfgHideShowExpectsList , DocAutoCfgHideShowUnexpectedItem , DocAutoCfgWrongLiteral ,
18- DocTestLiteral , DocTestTakesList , DocTestUnknown , DocUnknownAny , DocUnknownInclude ,
19- DocUnknownPasses , DocUnknownPlugins , DocUnknownSpotlight , ExpectedNameValue , ExpectedNoArgs ,
20- IllFormedAttributeInput , MalformedDoc ,
18+ DocAutoCfgHideShowExpectsList , DocAutoCfgHideShowNoIdentBeforeValues ,
19+ DocAutoCfgHideShowUnexpectedItem , DocAutoCfgHideShowUnexpectedItemAfterValues ,
20+ DocAutoCfgHideShowValuesMix , DocAutoCfgWrongLiteral , DocTestLiteral , DocTestTakesList ,
21+ DocTestUnknown , DocUnknownAny , DocUnknownInclude , DocUnknownPasses , DocUnknownPlugins ,
22+ DocUnknownSpotlight , ExpectedNameValue , ExpectedNoArgs , IllFormedAttributeInput , MalformedDoc ,
23+ } ;
24+ use crate :: parser:: {
25+ ArgParser , MetaItemListParser , MetaItemOrLitParser , MetaItemParser , OwnedPathParser ,
2126} ;
22- use crate :: parser:: { ArgParser , MetaItemOrLitParser , MetaItemParser , OwnedPathParser } ;
2327use crate :: session_diagnostics:: {
2428 DocAliasBadChar , DocAliasEmpty , DocAliasMalformed , DocAliasStartEnd , DocAttrNotCrateLevel ,
2529 DocAttributeNotAttribute , DocKeywordNotKeyword , UnusedDuplicate ,
@@ -304,6 +308,81 @@ impl DocParser {
304308 }
305309 }
306310
311+ // Parses the `doc(auto_cfg(hide/show(..., values())))` attribute.
312+ fn parse_auto_cfg_values (
313+ & self ,
314+ cx : & mut AcceptContext < ' _ , ' _ > ,
315+ list : & MetaItemListParser ,
316+ values : & mut Option < DocCfgHideShow > ,
317+ ) {
318+ let mut cfg_values = DocCfgHideShow :: new ( ) ;
319+
320+ let mut values_set = FxHashSet :: default ( ) ;
321+ for item in list. mixed ( ) {
322+ match item {
323+ // If it's a string literal, all good.
324+ MetaItemOrLitParser :: Lit ( MetaItemLit {
325+ kind : LitKind :: Str ( symbol, _) ,
326+ span,
327+ ..
328+ } ) => match & mut cfg_values {
329+ DocCfgHideShow :: Any ( any_span) => {
330+ cx. emit_lint (
331+ rustc_session:: lint:: builtin:: INVALID_DOC_ATTRIBUTES ,
332+ DocAutoCfgHideShowValuesMix { value_span : * span } ,
333+ * any_span,
334+ ) ;
335+ }
336+ DocCfgHideShow :: List ( symbols) => {
337+ if values_set. insert ( symbol) {
338+ symbols. push ( DocCfgHideShowValue :: new ( * symbol, * span) ) ;
339+ }
340+ }
341+ } ,
342+ // If it's any other kind of literal, then it's wrong and we emit a lint.
343+ MetaItemOrLitParser :: Lit ( lit) => cx. emit_lint (
344+ rustc_session:: lint:: builtin:: INVALID_DOC_ATTRIBUTES ,
345+ DocAutoCfgHideShowUnexpectedItem { attr_name : lit. symbol } ,
346+ lit. span ,
347+ ) ,
348+ // If it's a list, then only `any()` and `none()` are allowed and they must not
349+ // contain any item.
350+ MetaItemOrLitParser :: MetaItemParser ( sub_item) => {
351+ if let Some ( ident) = sub_item. ident ( )
352+ && [ sym:: any, sym:: none] . contains ( & ident. name )
353+ && let ArgParser :: List ( list) = sub_item. args ( )
354+ && list. mixed ( ) . count ( ) == 0
355+ {
356+ if ident. name == sym:: any {
357+ if let DocCfgHideShow :: List ( values) = & cfg_values
358+ && let Some ( value) = values. first ( )
359+ {
360+ cx. emit_lint (
361+ rustc_session:: lint:: builtin:: INVALID_DOC_ATTRIBUTES ,
362+ DocAutoCfgHideShowValuesMix { value_span : value. span } ,
363+ sub_item. span ( ) ,
364+ ) ;
365+ } else {
366+ cfg_values. merge_with ( & DocCfgHideShow :: Any ( sub_item. span ( ) ) ) ;
367+ }
368+ } else {
369+ cfg_values. push_none ( sub_item. span ( ) ) ;
370+ }
371+ } else {
372+ cx. emit_lint (
373+ rustc_session:: lint:: builtin:: INVALID_DOC_ATTRIBUTES ,
374+ DocAutoCfgHideShowUnexpectedItem {
375+ attr_name : sub_item. ident ( ) . unwrap ( ) . name ,
376+ } ,
377+ sub_item. span ( ) ,
378+ ) ;
379+ }
380+ }
381+ }
382+ }
383+ * values = Some ( cfg_values) ;
384+ }
385+
307386 fn parse_auto_cfg (
308387 & mut self ,
309388 cx : & mut AcceptContext < ' _ , ' _ > ,
@@ -315,7 +394,7 @@ impl DocParser {
315394 self . attribute . auto_cfg_change . push ( ( true , path. span ( ) ) ) ;
316395 }
317396 ArgParser :: List ( list) => {
318- for meta in list. mixed ( ) {
397+ ' main : for meta in list. mixed ( ) {
319398 let MetaItemOrLitParser :: MetaItemParser ( item) = meta else {
320399 cx. emit_lint (
321400 rustc_session:: lint:: builtin:: INVALID_DOC_ATTRIBUTES ,
@@ -324,6 +403,8 @@ impl DocParser {
324403 ) ;
325404 continue ;
326405 } ;
406+ // Only `hide` and `show` are allowed in `auto_cfg` if it's a list, and both
407+ // must be a list.
327408 let ( kind, attr_name) = match item. path ( ) . word_sym ( ) {
328409 Some ( sym:: hide) => ( HideOrShow :: Hide , sym:: hide) ,
329410 Some ( sym:: show) => ( HideOrShow :: Show , sym:: show) ,
@@ -345,56 +426,71 @@ impl DocParser {
345426 continue ;
346427 } ;
347428
348- let mut cfg_hide_show = CfgHideShow { kind, values : ThinVec :: new ( ) } ;
429+ let mut cfg_hide_show = CfgHideShow { kind, values : FxIndexMap :: default ( ) } ;
349430
431+ let mut cfg_names = FxHashSet :: default ( ) ;
432+ let mut values = None ;
350433 for item in list. mixed ( ) {
351434 let MetaItemOrLitParser :: MetaItemParser ( sub_item) = item else {
352435 cx. emit_lint (
353436 rustc_session:: lint:: builtin:: INVALID_DOC_ATTRIBUTES ,
354437 DocAutoCfgHideShowUnexpectedItem { attr_name } ,
355438 item. span ( ) ,
356439 ) ;
357- continue ;
440+ continue ' main ;
358441 } ;
359442 match sub_item. args ( ) {
360- a @ ( ArgParser :: NoArgs | ArgParser :: NameValue ( _ ) ) => {
443+ ArgParser :: NoArgs if values . is_none ( ) => {
361444 let Some ( name) = sub_item. path ( ) . word_sym ( ) else {
362- // FIXME: remove this method once merged and uncomment the line
363- // below instead.
364- // cx.expected_identifier(sub_item.path().span());
445+ cx. adcx ( ) . expected_identifier ( sub_item. path ( ) . span ( ) ) ;
446+ continue ' main;
447+ } ;
448+ cfg_names. insert ( name) ;
449+ }
450+ // The only accepted list is `values()`.
451+ ArgParser :: List ( list) if values. is_none ( ) => {
452+ let Some ( sym:: values) = sub_item. path ( ) . word_sym ( ) else {
453+ cx. adcx ( ) . expected_identifier ( sub_item. path ( ) . span ( ) ) ;
454+ continue ' main;
455+ } ;
456+ if cfg_names. is_empty ( ) {
365457 cx. emit_lint (
366458 rustc_session:: lint:: builtin:: INVALID_DOC_ATTRIBUTES ,
367- MalformedDoc ,
368- sub_item. path ( ) . span ( ) ,
369- ) ;
370- continue ;
371- } ;
372- if let Ok ( CfgEntry :: NameValue { name, value, .. } ) =
373- super :: cfg:: parse_name_value (
374- name,
375- sub_item. path ( ) . span ( ) ,
376- a. as_name_value ( ) ,
459+ DocAutoCfgHideShowNoIdentBeforeValues ,
377460 sub_item. span ( ) ,
378- cx,
379- )
380- {
381- cfg_hide_show. values . push ( CfgInfo {
382- name,
383- name_span : sub_item. path ( ) . span ( ) ,
384- // If `value` is `Some`, `a.name_value()` will always return
385- // `Some` as well.
386- value : value
387- . map ( |v| ( v, a. as_name_value ( ) . unwrap ( ) . value_span ) ) ,
388- } )
461+ ) ;
462+ continue ' main;
389463 }
464+ self . parse_auto_cfg_values ( cx, list, & mut values) ;
390465 }
391- _ => {
466+ // No `name = value` is allowed.
467+ ArgParser :: NameValue ( _) => {
392468 cx. emit_lint (
393469 rustc_session:: lint:: builtin:: INVALID_DOC_ATTRIBUTES ,
394470 DocAutoCfgHideShowUnexpectedItem { attr_name } ,
395471 sub_item. span ( ) ,
396472 ) ;
397- continue ;
473+ }
474+ // If `values()` was already used, no item should come after it.
475+ _ => {
476+ cx. emit_lint (
477+ rustc_session:: lint:: builtin:: INVALID_DOC_ATTRIBUTES ,
478+ DocAutoCfgHideShowUnexpectedItemAfterValues ,
479+ sub_item. span ( ) ,
480+ ) ;
481+ }
482+ }
483+ }
484+
485+ let values = values. unwrap_or ( DocCfgHideShow :: new_with_only_key ( item. span ( ) ) ) ;
486+ #[ allow( rustc:: potential_query_instability) ]
487+ for cfg_name in & cfg_names {
488+ match cfg_hide_show. values . entry ( * cfg_name) {
489+ IndexEntry :: Vacant ( v) => {
490+ v. insert ( values. clone ( ) ) ;
491+ }
492+ IndexEntry :: Occupied ( mut o) => {
493+ o. get_mut ( ) . merge_with ( & values) ;
398494 }
399495 }
400496 }
0 commit comments