Merge ref 'dc47a69ed9' from rust-lang/rust

Pull recent changes from https://github.com/rust-lang/rust via Josh.

Upstream ref: dc47a69ed9
Filtered ref: c2685dab4096194918f1cb9c503817d592c20e94
Upstream diff: f520900083...dc47a69ed9

This merge was created using https://github.com/rust-lang/josh-sync.
This commit is contained in:
The Miri Cronjob Bot 2025-12-13 05:02:59 +00:00
commit 96f2d590a7
416 changed files with 6618 additions and 5297 deletions

View file

@ -431,6 +431,7 @@ Lzu Tao <taolzu@gmail.com>
Maik Klein <maikklein@googlemail.com>
Maja Kądziołka <maya@compilercrim.es> <github@compilercrim.es>
Maja Kądziołka <maya@compilercrim.es> <kuba@kadziolka.net>
Makai <m4kai410@gmail.com>
Malo Jaffré <jaffre.malo@gmail.com>
Manish Goregaokar <manishsmail@gmail.com>
Mara Bos <m-ou.se@m-ou.se>

View file

@ -4011,7 +4011,6 @@ dependencies = [
"itertools",
"rustc_abi",
"rustc_ast",
"rustc_attr_parsing",
"rustc_data_structures",
"rustc_errors",
"rustc_fluent_macro",
@ -4433,7 +4432,6 @@ dependencies = [
"rustc_abi",
"rustc_ast",
"rustc_ast_lowering",
"rustc_ast_pretty",
"rustc_attr_parsing",
"rustc_data_structures",
"rustc_errors",
@ -4870,6 +4868,7 @@ dependencies = [
"indexmap",
"itertools",
"minifier",
"proc-macro2",
"pulldown-cmark-escape",
"regex",
"rustdoc-json-types",

View file

@ -141,16 +141,11 @@ impl Path {
/// Check if this path is potentially a trivial const arg, i.e., one that can _potentially_
/// be represented without an anon const in the HIR.
///
/// If `allow_mgca_arg` is true (as should be the case in most situations when
/// `#![feature(min_generic_const_args)]` is enabled), then this always returns true
/// because all paths are valid.
///
/// Otherwise, it returns true iff the path has exactly one segment, and it has no generic args
/// Returns true iff the path has exactly one segment, and it has no generic args
/// (i.e., it is _potentially_ a const parameter).
#[tracing::instrument(level = "debug", ret)]
pub fn is_potential_trivial_const_arg(&self, allow_mgca_arg: bool) -> bool {
allow_mgca_arg
|| self.segments.len() == 1 && self.segments.iter().all(|seg| seg.args.is_none())
pub fn is_potential_trivial_const_arg(&self) -> bool {
self.segments.len() == 1 && self.segments.iter().all(|seg| seg.args.is_none())
}
}
@ -1385,6 +1380,15 @@ pub enum UnsafeSource {
UserProvided,
}
/// Track whether under `feature(min_generic_const_args)` this anon const
/// was explicitly disambiguated as an anon const or not through the use of
/// `const { ... }` syntax.
#[derive(Clone, PartialEq, Encodable, Decodable, Debug, Copy, Walkable)]
pub enum MgcaDisambiguation {
AnonConst,
Direct,
}
/// A constant (expression) that's not an item or associated item,
/// but needs its own `DefId` for type-checking, const-eval, etc.
/// These are usually found nested inside types (e.g., array lengths)
@ -1394,6 +1398,7 @@ pub enum UnsafeSource {
pub struct AnonConst {
pub id: NodeId,
pub value: Box<Expr>,
pub mgca_disambiguation: MgcaDisambiguation,
}
/// An expression.
@ -1412,26 +1417,20 @@ impl Expr {
///
/// This will unwrap at most one block level (curly braces). After that, if the expression
/// is a path, it mostly dispatches to [`Path::is_potential_trivial_const_arg`].
/// See there for more info about `allow_mgca_arg`.
///
/// The only additional thing to note is that when `allow_mgca_arg` is false, this function
/// will only allow paths with no qself, before dispatching to the `Path` function of
/// the same name.
/// This function will only allow paths with no qself, before dispatching to the `Path`
/// function of the same name.
///
/// Does not ensure that the path resolves to a const param/item, the caller should check this.
/// This also does not consider macros, so it's only correct after macro-expansion.
pub fn is_potential_trivial_const_arg(&self, allow_mgca_arg: bool) -> bool {
pub fn is_potential_trivial_const_arg(&self) -> bool {
let this = self.maybe_unwrap_block();
if allow_mgca_arg {
matches!(this.kind, ExprKind::Path(..))
if let ExprKind::Path(None, path) = &this.kind
&& path.is_potential_trivial_const_arg()
{
true
} else {
if let ExprKind::Path(None, path) = &this.kind
&& path.is_potential_trivial_const_arg(allow_mgca_arg)
{
true
} else {
false
}
false
}
}

View file

@ -13,7 +13,9 @@ use crate::ast::{
Expr, ExprKind, LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit, NormalAttr, Path,
PathSegment, Safety,
};
use crate::token::{self, CommentKind, Delimiter, InvisibleOrigin, MetaVarKind, Token};
use crate::token::{
self, CommentKind, Delimiter, DocFragmentKind, InvisibleOrigin, MetaVarKind, Token,
};
use crate::tokenstream::{
DelimSpan, LazyAttrTokenStream, Spacing, TokenStream, TokenStreamIter, TokenTree,
};
@ -179,15 +181,21 @@ impl AttributeExt for Attribute {
}
/// Returns the documentation and its kind if this is a doc comment or a sugared doc comment.
/// * `///doc` returns `Some(("doc", CommentKind::Line))`.
/// * `/** doc */` returns `Some(("doc", CommentKind::Block))`.
/// * `#[doc = "doc"]` returns `Some(("doc", CommentKind::Line))`.
/// * `///doc` returns `Some(("doc", DocFragmentKind::Sugared(CommentKind::Line)))`.
/// * `/** doc */` returns `Some(("doc", DocFragmentKind::Sugared(CommentKind::Block)))`.
/// * `#[doc = "doc"]` returns `Some(("doc", DocFragmentKind::Raw))`.
/// * `#[doc(...)]` returns `None`.
fn doc_str_and_comment_kind(&self) -> Option<(Symbol, CommentKind)> {
fn doc_str_and_fragment_kind(&self) -> Option<(Symbol, DocFragmentKind)> {
match &self.kind {
AttrKind::DocComment(kind, data) => Some((*data, *kind)),
AttrKind::DocComment(kind, data) => Some((*data, DocFragmentKind::Sugared(*kind))),
AttrKind::Normal(normal) if normal.item.path == sym::doc => {
normal.item.value_str().map(|s| (s, CommentKind::Line))
if let Some(value) = normal.item.value_str()
&& let Some(value_span) = normal.item.value_span()
{
Some((value, DocFragmentKind::Raw(value_span)))
} else {
None
}
}
_ => None,
}
@ -220,6 +228,24 @@ impl AttributeExt for Attribute {
fn is_automatically_derived_attr(&self) -> bool {
self.has_name(sym::automatically_derived)
}
fn is_doc_hidden(&self) -> bool {
self.has_name(sym::doc)
&& self.meta_item_list().is_some_and(|l| list_contains_name(&l, sym::hidden))
}
fn is_doc_keyword_or_attribute(&self) -> bool {
if self.has_name(sym::doc)
&& let Some(items) = self.meta_item_list()
{
for item in items {
if item.has_name(sym::keyword) || item.has_name(sym::attribute) {
return true;
}
}
}
false
}
}
impl Attribute {
@ -300,6 +326,25 @@ impl AttrItem {
}
}
/// Returns the span in:
///
/// ```text
/// #[attribute = "value"]
/// ^^^^^^^
/// ```
///
/// It returns `None` in any other cases like:
///
/// ```text
/// #[attr("value")]
/// ```
fn value_span(&self) -> Option<Span> {
match &self.args {
AttrArgs::Eq { expr, .. } => Some(expr.span),
AttrArgs::Delimited(_) | AttrArgs::Empty => None,
}
}
pub fn meta(&self, span: Span) -> Option<MetaItem> {
Some(MetaItem {
unsafety: Safety::Default,
@ -820,7 +865,7 @@ pub trait AttributeExt: Debug {
/// * `/** doc */` returns `Some(("doc", CommentKind::Block))`.
/// * `#[doc = "doc"]` returns `Some(("doc", CommentKind::Line))`.
/// * `#[doc(...)]` returns `None`.
fn doc_str_and_comment_kind(&self) -> Option<(Symbol, CommentKind)>;
fn doc_str_and_fragment_kind(&self) -> Option<(Symbol, DocFragmentKind)>;
/// Returns outer or inner if this is a doc attribute or a sugared doc
/// comment, otherwise None.
@ -830,6 +875,12 @@ pub trait AttributeExt: Debug {
/// commented module (for inner doc) vs within its parent module (for outer
/// doc).
fn doc_resolution_scope(&self) -> Option<AttrStyle>;
/// Returns `true` if this attribute contains `doc(hidden)`.
fn is_doc_hidden(&self) -> bool;
/// Returns `true` is this attribute contains `doc(keyword)` or `doc(attribute)`.
fn is_doc_keyword_or_attribute(&self) -> bool;
}
// FIXME(fn_delegation): use function delegation instead of manually forwarding
@ -902,7 +953,7 @@ impl Attribute {
AttributeExt::is_proc_macro_attr(self)
}
pub fn doc_str_and_comment_kind(&self) -> Option<(Symbol, CommentKind)> {
AttributeExt::doc_str_and_comment_kind(self)
pub fn doc_str_and_fragment_kind(&self) -> Option<(Symbol, DocFragmentKind)> {
AttributeExt::doc_str_and_fragment_kind(self)
}
}

View file

@ -16,7 +16,31 @@ use rustc_span::{Ident, Symbol};
use crate::ast;
use crate::util::case::Case;
#[derive(Clone, Copy, PartialEq, Encodable, Decodable, Debug, HashStable_Generic)]
/// Represents the kind of doc comment it is, ie `///` or `#[doc = ""]`.
#[derive(Clone, Copy, PartialEq, Eq, Encodable, Decodable, Debug, HashStable_Generic)]
pub enum DocFragmentKind {
/// A sugared doc comment: `///` or `//!` or `/**` or `/*!`.
Sugared(CommentKind),
/// A "raw" doc comment: `#[doc = ""]`. The `Span` represents the string literal.
Raw(Span),
}
impl DocFragmentKind {
pub fn is_sugared(self) -> bool {
matches!(self, Self::Sugared(_))
}
/// If it is `Sugared`, it will return its associated `CommentKind`, otherwise it will return
/// `CommentKind::Line`.
pub fn comment_kind(self) -> CommentKind {
match self {
Self::Sugared(kind) => kind,
Self::Raw(_) => CommentKind::Line,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Encodable, Decodable, Debug, HashStable_Generic)]
pub enum CommentKind {
Line,
Block,

View file

@ -415,6 +415,7 @@ macro_rules! common_visitor_and_walkers {
UnsafeBinderCastKind,
BinOpKind,
BlockCheckMode,
MgcaDisambiguation,
BorrowKind,
BoundAsyncness,
BoundConstness,

View file

@ -489,7 +489,11 @@ impl<'hir> LoweringContext<'_, 'hir> {
arg
};
let anon_const = AnonConst { id: node_id, value: const_value };
let anon_const = AnonConst {
id: node_id,
value: const_value,
mgca_disambiguation: MgcaDisambiguation::AnonConst,
};
generic_args.push(AngleBracketedArg::Arg(GenericArg::Const(anon_const)));
} else {
real_args.push(arg);

View file

@ -1219,7 +1219,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
.and_then(|partial_res| partial_res.full_res())
{
if !res.matches_ns(Namespace::TypeNS)
&& path.is_potential_trivial_const_arg(false)
&& path.is_potential_trivial_const_arg()
{
debug!(
"lower_generic_arg: Lowering type argument as const argument: {:?}",
@ -2287,11 +2287,9 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
) -> &'hir hir::ConstArg<'hir> {
let tcx = self.tcx;
let ct_kind = if path
.is_potential_trivial_const_arg(tcx.features().min_generic_const_args())
&& (tcx.features().min_generic_const_args()
|| matches!(res, Res::Def(DefKind::ConstParam, _)))
{
let is_trivial_path = path.is_potential_trivial_const_arg()
&& matches!(res, Res::Def(DefKind::ConstParam, _));
let ct_kind = if is_trivial_path || tcx.features().min_generic_const_args() {
let qpath = self.lower_qpath(
ty_id,
&None,
@ -2370,6 +2368,53 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
}
}
#[instrument(level = "debug", skip(self), ret)]
fn lower_expr_to_const_arg_direct(&mut self, expr: &Expr) -> hir::ConstArg<'hir> {
let overly_complex_const = |this: &mut Self| {
let e = this.dcx().struct_span_err(
expr.span,
"complex const arguments must be placed inside of a `const` block",
);
ConstArg { hir_id: this.next_id(), kind: hir::ConstArgKind::Error(expr.span, e.emit()) }
};
match &expr.kind {
ExprKind::Path(qself, path) => {
let qpath = self.lower_qpath(
expr.id,
qself,
path,
ParamMode::Explicit,
AllowReturnTypeNotation::No,
// FIXME(mgca): update for `fn foo() -> Bar<FOO<impl Trait>>` support
ImplTraitContext::Disallowed(ImplTraitPosition::Path),
None,
);
ConstArg { hir_id: self.next_id(), kind: hir::ConstArgKind::Path(qpath) }
}
ExprKind::Underscore => ConstArg {
hir_id: self.lower_node_id(expr.id),
kind: hir::ConstArgKind::Infer(expr.span, ()),
},
ExprKind::Block(block, _) => {
if let [stmt] = block.stmts.as_slice()
&& let StmtKind::Expr(expr) = &stmt.kind
&& matches!(
expr.kind,
ExprKind::Block(..) | ExprKind::Path(..) | ExprKind::Struct(..)
)
{
return self.lower_expr_to_const_arg_direct(expr);
}
overly_complex_const(self)
}
_ => overly_complex_const(self),
}
}
/// See [`hir::ConstArg`] for when to use this function vs
/// [`Self::lower_anon_const_to_anon_const`].
fn lower_anon_const_to_const_arg(&mut self, anon: &AnonConst) -> &'hir hir::ConstArg<'hir> {
@ -2379,6 +2424,22 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
#[instrument(level = "debug", skip(self))]
fn lower_anon_const_to_const_arg_direct(&mut self, anon: &AnonConst) -> hir::ConstArg<'hir> {
let tcx = self.tcx;
// We cannot change parsing depending on feature gates available,
// we can only require feature gates to be active as a delayed check.
// Thus we just parse anon consts generally and make the real decision
// making in ast lowering.
// FIXME(min_generic_const_args): revisit once stable
if tcx.features().min_generic_const_args() {
return match anon.mgca_disambiguation {
MgcaDisambiguation::AnonConst => {
let lowered_anon = self.lower_anon_const_to_anon_const(anon);
ConstArg { hir_id: self.next_id(), kind: hir::ConstArgKind::Anon(lowered_anon) }
}
MgcaDisambiguation::Direct => self.lower_expr_to_const_arg_direct(&anon.value),
};
}
// Unwrap a block, so that e.g. `{ P }` is recognised as a parameter. Const arguments
// currently have to be wrapped in curly brackets, so it's necessary to special-case.
let expr = if let ExprKind::Block(block, _) = &anon.value.kind
@ -2390,12 +2451,12 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
} else {
&anon.value
};
let maybe_res =
self.resolver.get_partial_res(expr.id).and_then(|partial_res| partial_res.full_res());
if let ExprKind::Path(qself, path) = &expr.kind
&& path.is_potential_trivial_const_arg(tcx.features().min_generic_const_args())
&& (tcx.features().min_generic_const_args()
|| matches!(maybe_res, Some(Res::Def(DefKind::ConstParam, _))))
&& path.is_potential_trivial_const_arg()
&& matches!(maybe_res, Some(Res::Def(DefKind::ConstParam, _)))
{
let qpath = self.lower_qpath(
expr.id,
@ -2403,7 +2464,6 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
path,
ParamMode::Explicit,
AllowReturnTypeNotation::No,
// FIXME(mgca): update for `fn foo() -> Bar<FOO<impl Trait>>` support
ImplTraitContext::Disallowed(ImplTraitPosition::Path),
None,
);

View file

@ -517,6 +517,7 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session, features: &Features) {
gate_all!(fn_delegation, "functions delegation is not yet fully implemented");
gate_all!(postfix_match, "postfix match is experimental");
gate_all!(mut_ref, "mutable by-reference bindings are experimental");
gate_all!(min_generic_const_args, "unbraced const blocks as const args are experimental");
gate_all!(global_registration, "global registration is experimental");
gate_all!(return_type_notation, "return type notation is experimental");
gate_all!(pin_ergonomics, "pinned reference syntax is experimental");

View file

@ -10,7 +10,7 @@ use std::borrow::Cow;
use std::sync::Arc;
use rustc_ast::attr::AttrIdGenerator;
use rustc_ast::token::{self, CommentKind, Delimiter, Token, TokenKind};
use rustc_ast::token::{self, CommentKind, Delimiter, DocFragmentKind, Token, TokenKind};
use rustc_ast::tokenstream::{Spacing, TokenStream, TokenTree};
use rustc_ast::util::classify;
use rustc_ast::util::comments::{Comment, CommentStyle};
@ -381,15 +381,24 @@ fn space_between(tt1: &TokenTree, tt2: &TokenTree) -> bool {
}
pub fn doc_comment_to_string(
comment_kind: CommentKind,
fragment_kind: DocFragmentKind,
attr_style: ast::AttrStyle,
data: Symbol,
) -> String {
match (comment_kind, attr_style) {
(CommentKind::Line, ast::AttrStyle::Outer) => format!("///{data}"),
(CommentKind::Line, ast::AttrStyle::Inner) => format!("//!{data}"),
(CommentKind::Block, ast::AttrStyle::Outer) => format!("/**{data}*/"),
(CommentKind::Block, ast::AttrStyle::Inner) => format!("/*!{data}*/"),
match fragment_kind {
DocFragmentKind::Sugared(comment_kind) => match (comment_kind, attr_style) {
(CommentKind::Line, ast::AttrStyle::Outer) => format!("///{data}"),
(CommentKind::Line, ast::AttrStyle::Inner) => format!("//!{data}"),
(CommentKind::Block, ast::AttrStyle::Outer) => format!("/**{data}*/"),
(CommentKind::Block, ast::AttrStyle::Inner) => format!("/*!{data}*/"),
},
DocFragmentKind::Raw(_) => {
format!(
"#{}[doc = {:?}]",
if attr_style == ast::AttrStyle::Inner { "!" } else { "" },
data.to_string(),
)
}
}
}
@ -665,7 +674,11 @@ pub trait PrintState<'a>: std::ops::Deref<Target = pp::Printer> + std::ops::Dere
self.word("]");
}
ast::AttrKind::DocComment(comment_kind, data) => {
self.word(doc_comment_to_string(*comment_kind, attr.style, *data));
self.word(doc_comment_to_string(
DocFragmentKind::Sugared(*comment_kind),
attr.style,
*data,
));
self.hardbreak()
}
}
@ -1029,7 +1042,8 @@ pub trait PrintState<'a>: std::ops::Deref<Target = pp::Printer> + std::ops::Dere
/* Other */
token::DocComment(comment_kind, attr_style, data) => {
doc_comment_to_string(comment_kind, attr_style, data).into()
doc_comment_to_string(DocFragmentKind::Sugared(comment_kind), attr_style, data)
.into()
}
token::Eof => "<eof>".into(),
}

View file

@ -14,8 +14,29 @@ attr_parsing_deprecated_item_suggestion =
.help = add `#![feature(deprecated_suggestion)]` to the crate root
.note = see #94785 for more details
attr_parsing_doc_alias_bad_char =
{$char_} character isn't allowed in {$attr_str}
attr_parsing_doc_alias_empty =
{$attr_str} attribute cannot have empty value
attr_parsing_doc_alias_malformed =
doc alias attribute expects a string `#[doc(alias = "a")]` or a list of strings `#[doc(alias("a", "b"))]`
attr_parsing_doc_alias_start_end =
{$attr_str} cannot start or end with ' '
attr_parsing_doc_attribute_not_attribute =
nonexistent builtin attribute `{$attribute}` used in `#[doc(attribute = "...")]`
.help = only existing builtin attributes are allowed in core/std
attr_parsing_doc_keyword_not_keyword =
nonexistent keyword `{$keyword}` used in `#[doc(keyword = "...")]`
.help = only existing keywords are allowed in core/std
attr_parsing_empty_confusables =
expected at least one confusable name
attr_parsing_empty_link_name =
link name must not be empty
.label = empty link name

View file

@ -17,9 +17,9 @@ impl<S: Stage> CombineAttributeParser<S> for AllowInternalUnstableParser {
]);
const TEMPLATE: AttributeTemplate = template!(Word, List: &["feat1, feat2, ..."]);
fn extend<'c>(
cx: &'c mut AcceptContext<'_, '_, S>,
args: &'c ArgParser<'_>,
fn extend(
cx: &mut AcceptContext<'_, '_, S>,
args: &ArgParser,
) -> impl IntoIterator<Item = Self::Item> {
parse_unstable(cx, args, <Self as CombineAttributeParser<S>>::PATH[0])
.into_iter()
@ -39,9 +39,9 @@ impl<S: Stage> CombineAttributeParser<S> for UnstableFeatureBoundParser {
]);
const TEMPLATE: AttributeTemplate = template!(Word, List: &["feat1, feat2, ..."]);
fn extend<'c>(
cx: &'c mut AcceptContext<'_, '_, S>,
args: &'c ArgParser<'_>,
fn extend(
cx: &mut AcceptContext<'_, '_, S>,
args: &ArgParser,
) -> impl IntoIterator<Item = Self::Item> {
if !cx.features().staged_api() {
cx.emit_err(session_diagnostics::StabilityOutsideStd { span: cx.attr_span });
@ -67,17 +67,17 @@ impl<S: Stage> CombineAttributeParser<S> for AllowConstFnUnstableParser {
]);
const TEMPLATE: AttributeTemplate = template!(Word, List: &["feat1, feat2, ..."]);
fn extend<'c>(
cx: &'c mut AcceptContext<'_, '_, S>,
args: &'c ArgParser<'_>,
) -> impl IntoIterator<Item = Self::Item> + 'c {
fn extend(
cx: &mut AcceptContext<'_, '_, S>,
args: &ArgParser,
) -> impl IntoIterator<Item = Self::Item> {
parse_unstable(cx, args, <Self as CombineAttributeParser<S>>::PATH[0])
}
}
fn parse_unstable<S: Stage>(
cx: &AcceptContext<'_, '_, S>,
args: &ArgParser<'_>,
args: &ArgParser,
symbol: Symbol,
) -> impl IntoIterator<Item = Symbol> {
let mut res = Vec::new();

View file

@ -35,9 +35,9 @@ const CFG_ATTR_TEMPLATE: AttributeTemplate = template!(
"https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg_attr-attribute"
);
pub fn parse_cfg<'c, S: Stage>(
cx: &'c mut AcceptContext<'_, '_, S>,
args: &'c ArgParser<'_>,
pub fn parse_cfg<S: Stage>(
cx: &mut AcceptContext<'_, '_, S>,
args: &ArgParser,
) -> Option<CfgEntry> {
let ArgParser::List(list) = args else {
cx.expected_list(cx.attr_span);
@ -52,7 +52,7 @@ pub fn parse_cfg<'c, S: Stage>(
pub fn parse_cfg_entry<S: Stage>(
cx: &mut AcceptContext<'_, '_, S>,
item: &MetaItemOrLitParser<'_>,
item: &MetaItemOrLitParser,
) -> Result<CfgEntry, ErrorGuaranteed> {
Ok(match item {
MetaItemOrLitParser::MetaItemParser(meta) => match meta.args() {
@ -98,7 +98,7 @@ pub fn parse_cfg_entry<S: Stage>(
fn parse_cfg_entry_version<S: Stage>(
cx: &mut AcceptContext<'_, '_, S>,
list: &MetaItemListParser<'_>,
list: &MetaItemListParser,
meta_span: Span,
) -> Result<CfgEntry, ErrorGuaranteed> {
try_gate_cfg(sym::version, meta_span, cx.sess(), cx.features_option());
@ -130,7 +130,7 @@ fn parse_cfg_entry_version<S: Stage>(
fn parse_cfg_entry_target<S: Stage>(
cx: &mut AcceptContext<'_, '_, S>,
list: &MetaItemListParser<'_>,
list: &MetaItemListParser,
meta_span: Span,
) -> Result<CfgEntry, ErrorGuaranteed> {
if let Some(features) = cx.features_option()
@ -171,7 +171,7 @@ fn parse_cfg_entry_target<S: Stage>(
Ok(CfgEntry::All(result, list.span))
}
fn parse_name_value<S: Stage>(
pub(crate) fn parse_name_value<S: Stage>(
name: Symbol,
name_span: Span,
value: Option<&NameValueParser>,

View file

@ -2,10 +2,7 @@ use rustc_ast::{LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit, Nod
use rustc_ast_pretty::pprust;
use rustc_feature::{Features, GatedCfg, find_gated_cfg};
use rustc_hir::RustcVersion;
use rustc_hir::lints::AttributeLintKind;
use rustc_session::Session;
use rustc_session::config::ExpectedValues;
use rustc_session::lint::builtin::UNEXPECTED_CFGS;
use rustc_session::lint::{BuiltinLintDiag, Lint};
use rustc_session::parse::feature_err;
use rustc_span::{Span, Symbol, sym};
@ -37,44 +34,6 @@ pub struct Condition {
pub span: Span,
}
/// Tests if a cfg-pattern matches the cfg set
pub fn cfg_matches(
cfg: &MetaItemInner,
sess: &Session,
lint_emitter: impl CfgMatchesLintEmitter,
features: Option<&Features>,
) -> bool {
eval_condition(cfg, sess, features, &mut |cfg| {
try_gate_cfg(cfg.name, cfg.span, sess, features);
match sess.psess.check_config.expecteds.get(&cfg.name) {
Some(ExpectedValues::Some(values)) if !values.contains(&cfg.value) => {
lint_emitter.emit_span_lint(
sess,
UNEXPECTED_CFGS,
cfg.span,
BuiltinLintDiag::AttributeLint(AttributeLintKind::UnexpectedCfgValue(
(cfg.name, cfg.name_span),
cfg.value.map(|v| (v, cfg.value_span.unwrap())),
)),
);
}
None if sess.psess.check_config.exhaustive_names => {
lint_emitter.emit_span_lint(
sess,
UNEXPECTED_CFGS,
cfg.span,
BuiltinLintDiag::AttributeLint(AttributeLintKind::UnexpectedCfgName(
(cfg.name, cfg.name_span),
cfg.value.map(|v| (v, cfg.value_span.unwrap())),
)),
);
}
_ => { /* not unexpected */ }
}
sess.psess.config.contains(&(cfg.name, cfg.value))
})
}
pub fn try_gate_cfg(name: Symbol, span: Span, sess: &Session, features: Option<&Features>) {
let gate = find_gated_cfg(|sym| sym == name);
if let (Some(feats), Some(gated_cfg)) = (features, gate) {

View file

@ -23,7 +23,7 @@ impl<S: Stage> SingleAttributeParser<S> for OptimizeParser {
]);
const TEMPLATE: AttributeTemplate = template!(List: &["size", "speed", "none"]);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let Some(list) = args.list() else {
cx.expected_list(cx.attr_span);
return None;
@ -84,7 +84,7 @@ impl<S: Stage> SingleAttributeParser<S> for CoverageParser {
]);
const TEMPLATE: AttributeTemplate = template!(OneOf: &[sym::off, sym::on]);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let Some(args) = args.list() else {
cx.expected_specific_argument_and_list(cx.attr_span, &[sym::on, sym::off]);
return None;
@ -135,7 +135,7 @@ impl<S: Stage> SingleAttributeParser<S> for ExportNameParser {
]);
const TEMPLATE: AttributeTemplate = template!(NameValueStr: "name");
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let Some(nv) = args.name_value() else {
cx.expected_name_value(cx.attr_span, None);
return None;
@ -164,7 +164,7 @@ impl<S: Stage> SingleAttributeParser<S> for ObjcClassParser {
AllowedTargets::AllowList(&[Allow(Target::ForeignStatic)]);
const TEMPLATE: AttributeTemplate = template!(NameValueStr: "ClassName");
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let Some(nv) = args.name_value() else {
cx.expected_name_value(cx.attr_span, None);
return None;
@ -196,7 +196,7 @@ impl<S: Stage> SingleAttributeParser<S> for ObjcSelectorParser {
AllowedTargets::AllowList(&[Allow(Target::ForeignStatic)]);
const TEMPLATE: AttributeTemplate = template!(NameValueStr: "methodName");
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let Some(nv) = args.name_value() else {
cx.expected_name_value(cx.attr_span, None);
return None;
@ -472,10 +472,10 @@ impl<S: Stage> AttributeParser<S> for UsedParser {
}
}
fn parse_tf_attribute<'c, S: Stage>(
cx: &'c mut AcceptContext<'_, '_, S>,
args: &'c ArgParser<'_>,
) -> impl IntoIterator<Item = (Symbol, Span)> + 'c {
fn parse_tf_attribute<S: Stage>(
cx: &mut AcceptContext<'_, '_, S>,
args: &ArgParser,
) -> impl IntoIterator<Item = (Symbol, Span)> {
let mut features = Vec::new();
let ArgParser::List(list) = args else {
cx.expected_list(cx.attr_span);
@ -529,10 +529,10 @@ impl<S: Stage> CombineAttributeParser<S> for TargetFeatureParser {
};
const TEMPLATE: AttributeTemplate = template!(List: &["enable = \"feat1, feat2\""]);
fn extend<'c>(
cx: &'c mut AcceptContext<'_, '_, S>,
args: &'c ArgParser<'_>,
) -> impl IntoIterator<Item = Self::Item> + 'c {
fn extend(
cx: &mut AcceptContext<'_, '_, S>,
args: &ArgParser,
) -> impl IntoIterator<Item = Self::Item> {
parse_tf_attribute(cx, args)
}
@ -567,10 +567,10 @@ impl<S: Stage> CombineAttributeParser<S> for ForceTargetFeatureParser {
Allow(Target::Method(MethodKind::TraitImpl)),
]);
fn extend<'c>(
cx: &'c mut AcceptContext<'_, '_, S>,
args: &'c ArgParser<'_>,
) -> impl IntoIterator<Item = Self::Item> + 'c {
fn extend(
cx: &mut AcceptContext<'_, '_, S>,
args: &ArgParser,
) -> impl IntoIterator<Item = Self::Item> {
parse_tf_attribute(cx, args)
}
}
@ -599,7 +599,7 @@ impl<S: Stage> SingleAttributeParser<S> for SanitizeParser {
const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost;
const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error;
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let Some(list) = args.list() else {
cx.expected_list(cx.attr_span);
return None;

View file

@ -11,7 +11,7 @@ impl<S: Stage> SingleAttributeParser<S> for CrateNameParser {
const TEMPLATE: AttributeTemplate = template!(NameValueStr: "name");
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::CrateLevel;
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let ArgParser::NameValue(n) = args else {
cx.expected_name_value(cx.attr_span, None);
return None;
@ -35,7 +35,7 @@ impl<S: Stage> SingleAttributeParser<S> for RecursionLimitParser {
const TEMPLATE: AttributeTemplate = template!(NameValueStr: "N", "https://doc.rust-lang.org/reference/attributes/limits.html#the-recursion_limit-attribute");
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::CrateLevel;
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let ArgParser::NameValue(nv) = args else {
cx.expected_name_value(cx.attr_span, None);
return None;
@ -58,7 +58,7 @@ impl<S: Stage> SingleAttributeParser<S> for MoveSizeLimitParser {
const TEMPLATE: AttributeTemplate = template!(NameValueStr: "N");
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::CrateLevel;
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let ArgParser::NameValue(nv) = args else {
cx.expected_name_value(cx.attr_span, None);
return None;
@ -81,7 +81,7 @@ impl<S: Stage> SingleAttributeParser<S> for TypeLengthLimitParser {
const TEMPLATE: AttributeTemplate = template!(NameValueStr: "N");
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::CrateLevel;
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let ArgParser::NameValue(nv) = args else {
cx.expected_name_value(cx.attr_span, None);
return None;
@ -104,7 +104,7 @@ impl<S: Stage> SingleAttributeParser<S> for PatternComplexityLimitParser {
const TEMPLATE: AttributeTemplate = template!(NameValueStr: "N");
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::CrateLevel;
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let ArgParser::NameValue(nv) = args else {
cx.expected_name_value(cx.attr_span, None);
return None;
@ -154,7 +154,7 @@ impl<S: Stage> SingleAttributeParser<S> for WindowsSubsystemParser {
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::CrateLevel;
const TEMPLATE: AttributeTemplate = template!(NameValueStr: ["windows", "console"], "https://doc.rust-lang.org/reference/runtime.html#the-windows_subsystem-attribute");
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let Some(nv) = args.name_value() else {
cx.expected_name_value(
args.span().unwrap_or(cx.inner_span),

View file

@ -16,10 +16,10 @@ impl<S: Stage> CombineAttributeParser<S> for DebuggerViualizerParser {
type Item = DebugVisualizer;
const CONVERT: ConvertFn<Self::Item> = |v, _| AttributeKind::DebuggerVisualizer(v);
fn extend<'c>(
cx: &'c mut AcceptContext<'_, '_, S>,
args: &'c ArgParser<'_>,
) -> impl IntoIterator<Item = Self::Item> + 'c {
fn extend(
cx: &mut AcceptContext<'_, '_, S>,
args: &ArgParser,
) -> impl IntoIterator<Item = Self::Item> {
let Some(l) = args.list() else {
cx.expected_list(args.span().unwrap_or(cx.attr_span));
return None;

View file

@ -12,7 +12,7 @@ fn get<S: Stage>(
cx: &AcceptContext<'_, '_, S>,
name: Symbol,
param_span: Span,
arg: &ArgParser<'_>,
arg: &ArgParser,
item: &Option<Symbol>,
) -> Option<Symbol> {
if item.is_some() {
@ -68,7 +68,7 @@ impl<S: Stage> SingleAttributeParser<S> for DeprecationParser {
NameValueStr: "reason"
);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let features = cx.features();
let mut since = None;

View file

@ -0,0 +1,627 @@
use rustc_ast::ast::{AttrStyle, LitKind, MetaItemLit};
use rustc_feature::template;
use rustc_hir::attrs::{
AttributeKind, CfgEntry, CfgHideShow, CfgInfo, DocAttribute, DocInline, HideOrShow,
};
use rustc_hir::lints::AttributeLintKind;
use rustc_span::{Span, Symbol, edition, sym};
use thin_vec::ThinVec;
use super::prelude::{ALL_TARGETS, AllowedTargets};
use super::{AcceptMapping, AttributeParser};
use crate::context::{AcceptContext, FinalizeContext, Stage};
use crate::parser::{ArgParser, MetaItemOrLitParser, MetaItemParser, OwnedPathParser};
use crate::session_diagnostics::{
DocAliasBadChar, DocAliasEmpty, DocAliasMalformed, DocAliasStartEnd, DocAttributeNotAttribute,
DocKeywordNotKeyword,
};
fn check_keyword<S: Stage>(cx: &mut AcceptContext<'_, '_, S>, keyword: Symbol, span: Span) -> bool {
// FIXME: Once rustdoc can handle URL conflicts on case insensitive file systems, we
// can remove the `SelfTy` case here, remove `sym::SelfTy`, and update the
// `#[doc(keyword = "SelfTy")` attribute in `library/std/src/keyword_docs.rs`.
if keyword.is_reserved(|| edition::LATEST_STABLE_EDITION)
|| keyword.is_weak()
|| keyword == sym::SelfTy
{
return true;
}
cx.emit_err(DocKeywordNotKeyword { span, keyword });
false
}
fn check_attribute<S: Stage>(
cx: &mut AcceptContext<'_, '_, S>,
attribute: Symbol,
span: Span,
) -> bool {
// FIXME: This should support attributes with namespace like `diagnostic::do_not_recommend`.
if rustc_feature::BUILTIN_ATTRIBUTE_MAP.contains_key(&attribute) {
return true;
}
cx.emit_err(DocAttributeNotAttribute { span, attribute });
false
}
fn parse_keyword_and_attribute<S, F>(
cx: &mut AcceptContext<'_, '_, S>,
path: &OwnedPathParser,
args: &ArgParser,
attr_value: &mut Option<(Symbol, Span)>,
callback: F,
) where
S: Stage,
F: FnOnce(&mut AcceptContext<'_, '_, S>, Symbol, Span) -> bool,
{
let Some(nv) = args.name_value() else {
cx.expected_name_value(args.span().unwrap_or(path.span()), path.word_sym());
return;
};
let Some(value) = nv.value_as_str() else {
cx.expected_string_literal(nv.value_span, Some(nv.value_as_lit()));
return;
};
if !callback(cx, value, nv.value_span) {
return;
}
if attr_value.is_some() {
cx.duplicate_key(path.span(), path.word_sym().unwrap());
return;
}
*attr_value = Some((value, path.span()));
}
#[derive(Default, Debug)]
pub(crate) struct DocParser {
attribute: DocAttribute,
nb_doc_attrs: usize,
}
impl DocParser {
fn parse_single_test_doc_attr_item<S: Stage>(
&mut self,
cx: &mut AcceptContext<'_, '_, S>,
mip: &MetaItemParser,
) {
let path = mip.path();
let args = mip.args();
match path.word_sym() {
Some(sym::no_crate_inject) => {
if let Err(span) = args.no_args() {
cx.expected_no_args(span);
return;
}
if self.attribute.no_crate_inject.is_some() {
cx.duplicate_key(path.span(), sym::no_crate_inject);
return;
}
self.attribute.no_crate_inject = Some(path.span())
}
Some(sym::attr) => {
let Some(list) = args.list() else {
cx.expected_list(cx.attr_span);
return;
};
// FIXME: convert list into a Vec of `AttributeKind` because current code is awful.
for attr in list.mixed() {
self.attribute.test_attrs.push(attr.span());
}
}
Some(name) => {
cx.emit_lint(
rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
AttributeLintKind::DocTestUnknown { name },
path.span(),
);
}
None => {
cx.emit_lint(
rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
AttributeLintKind::DocTestLiteral,
path.span(),
);
}
}
}
fn add_alias<S: Stage>(
&mut self,
cx: &mut AcceptContext<'_, '_, S>,
alias: Symbol,
span: Span,
) {
let attr_str = "`#[doc(alias = \"...\")]`";
if alias == sym::empty {
cx.emit_err(DocAliasEmpty { span, attr_str });
return;
}
let alias_str = alias.as_str();
if let Some(c) =
alias_str.chars().find(|&c| c == '"' || c == '\'' || (c.is_whitespace() && c != ' '))
{
cx.emit_err(DocAliasBadChar { span, attr_str, char_: c });
return;
}
if alias_str.starts_with(' ') || alias_str.ends_with(' ') {
cx.emit_err(DocAliasStartEnd { span, attr_str });
return;
}
if let Some(first_definition) = self.attribute.aliases.get(&alias).copied() {
cx.emit_lint(
rustc_session::lint::builtin::UNUSED_ATTRIBUTES,
AttributeLintKind::DuplicateDocAlias { first_definition },
span,
);
}
self.attribute.aliases.insert(alias, span);
}
fn parse_alias<S: Stage>(
&mut self,
cx: &mut AcceptContext<'_, '_, S>,
path: &OwnedPathParser,
args: &ArgParser,
) {
match args {
ArgParser::NoArgs => {
cx.emit_err(DocAliasMalformed { span: args.span().unwrap_or(path.span()) });
}
ArgParser::List(list) => {
for i in list.mixed() {
let Some(alias) = i.lit().and_then(|i| i.value_str()) else {
cx.expected_string_literal(i.span(), i.lit());
continue;
};
self.add_alias(cx, alias, i.span());
}
}
ArgParser::NameValue(nv) => {
let Some(alias) = nv.value_as_str() else {
cx.expected_string_literal(nv.value_span, Some(nv.value_as_lit()));
return;
};
self.add_alias(cx, alias, nv.value_span);
}
}
}
fn parse_inline<S: Stage>(
&mut self,
cx: &mut AcceptContext<'_, '_, S>,
path: &OwnedPathParser,
args: &ArgParser,
inline: DocInline,
) {
if let Err(span) = args.no_args() {
cx.expected_no_args(span);
return;
}
self.attribute.inline.push((inline, path.span()));
}
fn parse_cfg<S: Stage>(&mut self, cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) {
// This function replaces cases like `cfg(all())` with `true`.
fn simplify_cfg(cfg_entry: &mut CfgEntry) {
match cfg_entry {
CfgEntry::All(cfgs, span) if cfgs.is_empty() => {
*cfg_entry = CfgEntry::Bool(true, *span)
}
CfgEntry::Any(cfgs, span) if cfgs.is_empty() => {
*cfg_entry = CfgEntry::Bool(false, *span)
}
CfgEntry::Not(cfg, _) => simplify_cfg(cfg),
_ => {}
}
}
if let Some(mut cfg_entry) = super::cfg::parse_cfg(cx, args) {
simplify_cfg(&mut cfg_entry);
self.attribute.cfg.push(cfg_entry);
}
}
fn parse_auto_cfg<S: Stage>(
&mut self,
cx: &mut AcceptContext<'_, '_, S>,
path: &OwnedPathParser,
args: &ArgParser,
) {
match args {
ArgParser::NoArgs => {
self.attribute.auto_cfg_change.push((true, path.span()));
}
ArgParser::List(list) => {
for meta in list.mixed() {
let MetaItemOrLitParser::MetaItemParser(item) = meta else {
cx.emit_lint(
rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
AttributeLintKind::DocAutoCfgExpectsHideOrShow,
meta.span(),
);
continue;
};
let (kind, attr_name) = match item.path().word_sym() {
Some(sym::hide) => (HideOrShow::Hide, sym::hide),
Some(sym::show) => (HideOrShow::Show, sym::show),
_ => {
cx.emit_lint(
rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
AttributeLintKind::DocAutoCfgExpectsHideOrShow,
item.span(),
);
continue;
}
};
let ArgParser::List(list) = item.args() else {
cx.emit_lint(
rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
AttributeLintKind::DocAutoCfgHideShowExpectsList { attr_name },
item.span(),
);
continue;
};
let mut cfg_hide_show = CfgHideShow { kind, values: ThinVec::new() };
for item in list.mixed() {
let MetaItemOrLitParser::MetaItemParser(sub_item) = item else {
cx.emit_lint(
rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
AttributeLintKind::DocAutoCfgHideShowUnexpectedItem { attr_name },
item.span(),
);
continue;
};
match sub_item.args() {
a @ (ArgParser::NoArgs | ArgParser::NameValue(_)) => {
let Some(name) = sub_item.path().word_sym() else {
cx.expected_identifier(sub_item.path().span());
continue;
};
if let Ok(CfgEntry::NameValue { name, value, .. }) =
super::cfg::parse_name_value(
name,
sub_item.path().span(),
a.name_value(),
sub_item.span(),
cx,
)
{
cfg_hide_show.values.push(CfgInfo {
name,
name_span: sub_item.path().span(),
// If `value` is `Some`, `a.name_value()` will always return
// `Some` as well.
value: value
.map(|v| (v, a.name_value().unwrap().value_span)),
})
}
}
_ => {
cx.emit_lint(
rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
AttributeLintKind::DocAutoCfgHideShowUnexpectedItem {
attr_name,
},
sub_item.span(),
);
continue;
}
}
}
self.attribute.auto_cfg.push((cfg_hide_show, path.span()));
}
}
ArgParser::NameValue(nv) => {
let MetaItemLit { kind: LitKind::Bool(bool_value), span, .. } = nv.value_as_lit()
else {
cx.emit_lint(
rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
AttributeLintKind::DocAutoCfgWrongLiteral,
nv.value_span,
);
return;
};
self.attribute.auto_cfg_change.push((*bool_value, *span));
}
}
}
fn parse_single_doc_attr_item<S: Stage>(
&mut self,
cx: &mut AcceptContext<'_, '_, S>,
mip: &MetaItemParser,
) {
let path = mip.path();
let args = mip.args();
macro_rules! no_args {
($ident: ident) => {{
if let Err(span) = args.no_args() {
cx.expected_no_args(span);
return;
}
// FIXME: It's errorring when the attribute is passed multiple times on the command
// line.
// The right fix for this would be to only check this rule if the attribute is
// not set on the command line but directly in the code.
// if self.attribute.$ident.is_some() {
// cx.duplicate_key(path.span(), path.word_sym().unwrap());
// return;
// }
self.attribute.$ident = Some(path.span());
}};
}
macro_rules! string_arg {
($ident: ident) => {{
let Some(nv) = args.name_value() else {
cx.expected_name_value(args.span().unwrap_or(path.span()), path.word_sym());
return;
};
let Some(s) = nv.value_as_str() else {
cx.expected_string_literal(nv.value_span, Some(nv.value_as_lit()));
return;
};
// FIXME: It's errorring when the attribute is passed multiple times on the command
// line.
// The right fix for this would be to only check this rule if the attribute is
// not set on the command line but directly in the code.
// if self.attribute.$ident.is_some() {
// cx.duplicate_key(path.span(), path.word_sym().unwrap());
// return;
// }
self.attribute.$ident = Some((s, path.span()));
}};
}
match path.word_sym() {
Some(sym::alias) => self.parse_alias(cx, path, args),
Some(sym::hidden) => no_args!(hidden),
Some(sym::html_favicon_url) => string_arg!(html_favicon_url),
Some(sym::html_logo_url) => string_arg!(html_logo_url),
Some(sym::html_no_source) => no_args!(html_no_source),
Some(sym::html_playground_url) => string_arg!(html_playground_url),
Some(sym::html_root_url) => string_arg!(html_root_url),
Some(sym::issue_tracker_base_url) => string_arg!(issue_tracker_base_url),
Some(sym::inline) => self.parse_inline(cx, path, args, DocInline::Inline),
Some(sym::no_inline) => self.parse_inline(cx, path, args, DocInline::NoInline),
Some(sym::masked) => no_args!(masked),
Some(sym::cfg) => self.parse_cfg(cx, args),
Some(sym::notable_trait) => no_args!(notable_trait),
Some(sym::keyword) => parse_keyword_and_attribute(
cx,
path,
args,
&mut self.attribute.keyword,
check_keyword,
),
Some(sym::attribute) => parse_keyword_and_attribute(
cx,
path,
args,
&mut self.attribute.attribute,
check_attribute,
),
Some(sym::fake_variadic) => no_args!(fake_variadic),
Some(sym::search_unbox) => no_args!(search_unbox),
Some(sym::rust_logo) => no_args!(rust_logo),
Some(sym::auto_cfg) => self.parse_auto_cfg(cx, path, args),
Some(sym::test) => {
let Some(list) = args.list() else {
cx.emit_lint(
rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
AttributeLintKind::DocTestTakesList,
args.span().unwrap_or(path.span()),
);
return;
};
for i in list.mixed() {
match i {
MetaItemOrLitParser::MetaItemParser(mip) => {
self.parse_single_test_doc_attr_item(cx, mip);
}
MetaItemOrLitParser::Lit(lit) => {
cx.unexpected_literal(lit.span);
}
MetaItemOrLitParser::Err(..) => {
// already had an error here, move on.
}
}
}
}
Some(sym::spotlight) => {
cx.emit_lint(
rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
AttributeLintKind::DocUnknownSpotlight { span: path.span() },
path.span(),
);
}
Some(sym::include) if let Some(nv) = args.name_value() => {
let inner = match cx.attr_style {
AttrStyle::Outer => "",
AttrStyle::Inner => "!",
};
cx.emit_lint(
rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
AttributeLintKind::DocUnknownInclude {
inner,
value: nv.value_as_lit().symbol,
span: path.span(),
},
path.span(),
);
}
Some(name @ (sym::passes | sym::no_default_passes)) => {
cx.emit_lint(
rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
AttributeLintKind::DocUnknownPasses { name, span: path.span() },
path.span(),
);
}
Some(sym::plugins) => {
cx.emit_lint(
rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
AttributeLintKind::DocUnknownPlugins { span: path.span() },
path.span(),
);
}
Some(name) => {
cx.emit_lint(
rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
AttributeLintKind::DocUnknownAny { name },
path.span(),
);
}
None => {
let full_name =
path.segments().map(|s| s.as_str()).intersperse("::").collect::<String>();
cx.emit_lint(
rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
AttributeLintKind::DocUnknownAny { name: Symbol::intern(&full_name) },
path.span(),
);
}
}
}
fn accept_single_doc_attr<S: Stage>(
&mut self,
cx: &mut AcceptContext<'_, '_, S>,
args: &ArgParser,
) {
match args {
ArgParser::NoArgs => {
let suggestions = cx.suggestions();
let span = cx.attr_span;
cx.emit_lint(
rustc_session::lint::builtin::ILL_FORMED_ATTRIBUTE_INPUT,
AttributeLintKind::IllFormedAttributeInput { suggestions, docs: None },
span,
);
}
ArgParser::List(items) => {
for i in items.mixed() {
match i {
MetaItemOrLitParser::MetaItemParser(mip) => {
self.nb_doc_attrs += 1;
self.parse_single_doc_attr_item(cx, mip);
}
MetaItemOrLitParser::Lit(lit) => {
cx.expected_name_value(lit.span, None);
}
MetaItemOrLitParser::Err(..) => {
// already had an error here, move on.
}
}
}
}
ArgParser::NameValue(nv) => {
if nv.value_as_str().is_none() {
cx.expected_string_literal(nv.value_span, Some(nv.value_as_lit()));
} else {
unreachable!(
"Should have been handled at the same time as sugar-syntaxed doc comments"
);
}
}
}
}
}
impl<S: Stage> AttributeParser<S> for DocParser {
const ATTRIBUTES: AcceptMapping<Self, S> = &[(
&[sym::doc],
template!(
List: &[
"alias",
"attribute",
"hidden",
"html_favicon_url",
"html_logo_url",
"html_no_source",
"html_playground_url",
"html_root_url",
"issue_tracker_base_url",
"inline",
"no_inline",
"masked",
"cfg",
"notable_trait",
"keyword",
"fake_variadic",
"search_unbox",
"rust_logo",
"auto_cfg",
"test",
"spotlight",
"include",
"no_default_passes",
"passes",
"plugins",
],
NameValueStr: "string"
),
|this, cx, args| {
this.accept_single_doc_attr(cx, args);
},
)];
// FIXME: Currently emitted from 2 different places, generating duplicated warnings.
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS);
// const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowListWarnRest(&[
// Allow(Target::ExternCrate),
// Allow(Target::Use),
// Allow(Target::Static),
// Allow(Target::Const),
// Allow(Target::Fn),
// Allow(Target::Mod),
// Allow(Target::ForeignMod),
// Allow(Target::TyAlias),
// Allow(Target::Enum),
// Allow(Target::Variant),
// Allow(Target::Struct),
// Allow(Target::Field),
// Allow(Target::Union),
// Allow(Target::Trait),
// Allow(Target::TraitAlias),
// Allow(Target::Impl { of_trait: true }),
// Allow(Target::Impl { of_trait: false }),
// Allow(Target::AssocConst),
// Allow(Target::Method(MethodKind::Inherent)),
// Allow(Target::Method(MethodKind::Trait { body: true })),
// Allow(Target::Method(MethodKind::Trait { body: false })),
// Allow(Target::Method(MethodKind::TraitImpl)),
// Allow(Target::AssocTy),
// Allow(Target::ForeignFn),
// Allow(Target::ForeignStatic),
// Allow(Target::ForeignTy),
// Allow(Target::MacroDef),
// Allow(Target::Crate),
// Error(Target::WherePredicate),
// ]);
fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
if self.nb_doc_attrs != 0 {
Some(AttributeKind::Doc(Box::new(self.attribute)))
} else {
None
}
}
}

View file

@ -15,7 +15,7 @@ impl<S: Stage> SingleAttributeParser<S> for DummyParser {
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS);
const TEMPLATE: AttributeTemplate = template!(Word); // Anything, really
fn convert(_: &mut AcceptContext<'_, '_, S>, _: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(_: &mut AcceptContext<'_, '_, S>, _: &ArgParser) -> Option<AttributeKind> {
Some(AttributeKind::Dummy)
}
}

View file

@ -34,7 +34,7 @@ impl<S: Stage> SingleAttributeParser<S> for InlineParser {
"https://doc.rust-lang.org/reference/attributes/codegen.html#the-inline-attribute"
);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
match args {
ArgParser::NoArgs => Some(AttributeKind::Inline(InlineAttr::Hint, cx.attr_span)),
ArgParser::List(list) => {
@ -77,7 +77,7 @@ impl<S: Stage> SingleAttributeParser<S> for RustcForceInlineParser {
const TEMPLATE: AttributeTemplate = template!(Word, List: &["reason"], NameValueStr: "reason");
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let reason = match args {
ArgParser::NoArgs => None,
ArgParser::List(list) => {

View file

@ -33,7 +33,7 @@ impl<S: Stage> SingleAttributeParser<S> for LinkNameParser {
"https://doc.rust-lang.org/reference/items/external-blocks.html#the-link_name-attribute"
);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let Some(nv) = args.name_value() else {
cx.expected_name_value(cx.attr_span, None);
return None;
@ -62,10 +62,10 @@ impl<S: Stage> CombineAttributeParser<S> for LinkParser {
], "https://doc.rust-lang.org/reference/items/external-blocks.html#the-link-attribute");
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); //FIXME Still checked fully in `check_attr.rs`
fn extend<'c>(
cx: &'c mut AcceptContext<'_, '_, S>,
args: &'c ArgParser<'_>,
) -> impl IntoIterator<Item = Self::Item> + 'c {
fn extend(
cx: &mut AcceptContext<'_, '_, S>,
args: &ArgParser,
) -> impl IntoIterator<Item = Self::Item> {
let items = match args {
ArgParser::List(list) => list,
// This is an edgecase added because making this a hard error would break too many crates
@ -242,7 +242,7 @@ impl<S: Stage> CombineAttributeParser<S> for LinkParser {
impl LinkParser {
fn parse_link_name<S: Stage>(
item: &MetaItemParser<'_>,
item: &MetaItemParser,
name: &mut Option<(Symbol, Span)>,
cx: &mut AcceptContext<'_, '_, S>,
) -> bool {
@ -267,7 +267,7 @@ impl LinkParser {
}
fn parse_link_kind<S: Stage>(
item: &MetaItemParser<'_>,
item: &MetaItemParser,
kind: &mut Option<NativeLibKind>,
cx: &mut AcceptContext<'_, '_, S>,
sess: &Session,
@ -347,7 +347,7 @@ impl LinkParser {
}
fn parse_link_modifiers<S: Stage>(
item: &MetaItemParser<'_>,
item: &MetaItemParser,
modifiers: &mut Option<(Symbol, Span)>,
cx: &mut AcceptContext<'_, '_, S>,
) -> bool {
@ -368,7 +368,7 @@ impl LinkParser {
}
fn parse_link_cfg<S: Stage>(
item: &MetaItemParser<'_>,
item: &MetaItemParser,
cfg: &mut Option<CfgEntry>,
cx: &mut AcceptContext<'_, '_, S>,
sess: &Session,
@ -400,7 +400,7 @@ impl LinkParser {
}
fn parse_link_wasm_import_module<S: Stage>(
item: &MetaItemParser<'_>,
item: &MetaItemParser,
wasm_import_module: &mut Option<(Symbol, Span)>,
cx: &mut AcceptContext<'_, '_, S>,
) -> bool {
@ -421,7 +421,7 @@ impl LinkParser {
}
fn parse_link_import_name_type<S: Stage>(
item: &MetaItemParser<'_>,
item: &MetaItemParser,
import_name_type: &mut Option<(PeImportNameType, Span)>,
cx: &mut AcceptContext<'_, '_, S>,
) -> bool {
@ -478,7 +478,7 @@ impl<S: Stage> SingleAttributeParser<S> for LinkSectionParser {
"https://doc.rust-lang.org/reference/abi.html#the-link_section-attribute"
);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let Some(nv) = args.name_value() else {
cx.expected_name_value(cx.attr_span, None);
return None;
@ -551,7 +551,7 @@ impl<S: Stage> SingleAttributeParser<S> for LinkOrdinalParser {
"https://doc.rust-lang.org/reference/items/external-blocks.html#the-link_ordinal-attribute"
);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let ordinal = parse_single_integer(cx, args)?;
// According to the table at
@ -607,7 +607,7 @@ impl<S: Stage> SingleAttributeParser<S> for LinkageParser {
"weak_odr",
]);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let Some(name_value) = args.name_value() else {
cx.expected_name_value(cx.attr_span, Some(sym::linkage));
return None;

View file

@ -148,7 +148,7 @@ impl<S: Stage> SingleAttributeParser<S> for MacroExportParser {
Error(Target::Crate),
]);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let local_inner_macros = match args {
ArgParser::NoArgs => false,
ArgParser::List(list) => {

View file

@ -39,6 +39,7 @@ pub(crate) mod confusables;
pub(crate) mod crate_level;
pub(crate) mod debugger;
pub(crate) mod deprecation;
pub(crate) mod doc;
pub(crate) mod dummy;
pub(crate) mod inline;
pub(crate) mod link_attrs;
@ -61,7 +62,7 @@ pub(crate) mod traits;
pub(crate) mod transparency;
pub(crate) mod util;
type AcceptFn<T, S> = for<'sess> fn(&mut T, &mut AcceptContext<'_, 'sess, S>, &ArgParser<'_>);
type AcceptFn<T, S> = for<'sess> fn(&mut T, &mut AcceptContext<'_, 'sess, S>, &ArgParser);
type AcceptMapping<T, S> = &'static [(&'static [Symbol], AttributeTemplate, AcceptFn<T, S>)];
/// An [`AttributeParser`] is a type which searches for syntactic attributes.
@ -132,7 +133,7 @@ pub(crate) trait SingleAttributeParser<S: Stage>: 'static {
const TEMPLATE: AttributeTemplate;
/// Converts a single syntactical attribute to a single semantic attribute, or [`AttributeKind`]
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind>;
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind>;
}
/// Use in combination with [`SingleAttributeParser`].
@ -281,7 +282,7 @@ impl<T: NoArgsAttributeParser<S>, S: Stage> SingleAttributeParser<S> for Without
const ALLOWED_TARGETS: AllowedTargets = T::ALLOWED_TARGETS;
const TEMPLATE: AttributeTemplate = template!(Word);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
if let Err(span) = args.no_args() {
cx.expected_no_args(span);
}
@ -314,10 +315,10 @@ pub(crate) trait CombineAttributeParser<S: Stage>: 'static {
const TEMPLATE: AttributeTemplate;
/// Converts a single syntactical attribute to a number of elements of the semantic attribute, or [`AttributeKind`]
fn extend<'c>(
cx: &'c mut AcceptContext<'_, '_, S>,
args: &'c ArgParser<'_>,
) -> impl IntoIterator<Item = Self::Item> + 'c;
fn extend(
cx: &mut AcceptContext<'_, '_, S>,
args: &ArgParser,
) -> impl IntoIterator<Item = Self::Item>;
}
/// Use in combination with [`CombineAttributeParser`].

View file

@ -29,7 +29,7 @@ impl<S: Stage> SingleAttributeParser<S> for MustUseParser {
"https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute"
);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
Some(AttributeKind::MustUse {
span: cx.attr_span,
reason: match args {

View file

@ -13,7 +13,7 @@ impl<S: Stage> SingleAttributeParser<S> for PathParser {
"https://doc.rust-lang.org/reference/items/modules.html#the-path-attribute"
);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let Some(nv) = args.name_value() else {
cx.expected_name_value(cx.attr_span, None);
return None;

View file

@ -30,7 +30,7 @@ impl<S: Stage> SingleAttributeParser<S> for ProcMacroDeriveParser {
"https://doc.rust-lang.org/reference/procedural-macros.html#derive-macros"
);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let (trait_name, helper_attrs) = parse_derive_like(cx, args, true)?;
Some(AttributeKind::ProcMacroDerive {
trait_name: trait_name.expect("Trait name is mandatory, so it is present"),
@ -49,7 +49,7 @@ impl<S: Stage> SingleAttributeParser<S> for RustcBuiltinMacroParser {
const TEMPLATE: AttributeTemplate =
template!(List: &["TraitName", "TraitName, attributes(name1, name2, ...)"]);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let (builtin_name, helper_attrs) = parse_derive_like(cx, args, false)?;
Some(AttributeKind::RustcBuiltinMacro { builtin_name, helper_attrs, span: cx.attr_span })
}
@ -57,7 +57,7 @@ impl<S: Stage> SingleAttributeParser<S> for RustcBuiltinMacroParser {
fn parse_derive_like<S: Stage>(
cx: &mut AcceptContext<'_, '_, S>,
args: &ArgParser<'_>,
args: &ArgParser,
trait_name_mandatory: bool,
) -> Option<(Option<Symbol>, ThinVec<Symbol>)> {
let Some(list) = args.list() else {

View file

@ -25,7 +25,7 @@ impl<S: Stage> SingleAttributeParser<S> for CustomMirParser {
const TEMPLATE: AttributeTemplate = template!(List: &[r#"dialect = "...", phase = "...""#]);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let Some(list) = args.list() else {
cx.expected_list(cx.attr_span);
return None;
@ -70,7 +70,7 @@ impl<S: Stage> SingleAttributeParser<S> for CustomMirParser {
fn extract_value<S: Stage>(
cx: &mut AcceptContext<'_, '_, S>,
key: Symbol,
arg: &ArgParser<'_>,
arg: &ArgParser,
span: Span,
out_val: &mut Option<(Symbol, Span)>,
failed: &mut bool,

View file

@ -26,10 +26,10 @@ impl<S: Stage> CombineAttributeParser<S> for ReprParser {
"https://doc.rust-lang.org/reference/type-layout.html#representations"
);
fn extend<'c>(
cx: &'c mut AcceptContext<'_, '_, S>,
args: &'c ArgParser<'_>,
) -> impl IntoIterator<Item = Self::Item> + 'c {
fn extend(
cx: &mut AcceptContext<'_, '_, S>,
args: &ArgParser,
) -> impl IntoIterator<Item = Self::Item> {
let mut reprs = Vec::new();
let Some(list) = args.list() else {
@ -98,10 +98,7 @@ fn int_type_of_word(s: Symbol) -> Option<IntType> {
}
}
fn parse_repr<S: Stage>(
cx: &AcceptContext<'_, '_, S>,
param: &MetaItemParser<'_>,
) -> Option<ReprAttr> {
fn parse_repr<S: Stage>(cx: &AcceptContext<'_, '_, S>, param: &MetaItemParser) -> Option<ReprAttr> {
use ReprAttr::*;
// FIXME(jdonszelmann): invert the parsing here to match on the word first and then the
@ -192,7 +189,7 @@ enum AlignKind {
fn parse_repr_align<S: Stage>(
cx: &AcceptContext<'_, '_, S>,
list: &MetaItemListParser<'_>,
list: &MetaItemListParser,
param_span: Span,
align_kind: AlignKind,
) -> Option<ReprAttr> {
@ -278,11 +275,7 @@ impl AlignParser {
const PATH: &'static [Symbol] = &[sym::rustc_align];
const TEMPLATE: AttributeTemplate = template!(List: &["<alignment in bytes>"]);
fn parse<'c, S: Stage>(
&mut self,
cx: &'c mut AcceptContext<'_, '_, S>,
args: &'c ArgParser<'_>,
) {
fn parse<S: Stage>(&mut self, cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) {
match args {
ArgParser::NoArgs | ArgParser::NameValue(_) => {
cx.expected_list(cx.attr_span);
@ -339,11 +332,7 @@ impl AlignStaticParser {
const PATH: &'static [Symbol] = &[sym::rustc_align_static];
const TEMPLATE: AttributeTemplate = AlignParser::TEMPLATE;
fn parse<'c, S: Stage>(
&mut self,
cx: &'c mut AcceptContext<'_, '_, S>,
args: &'c ArgParser<'_>,
) {
fn parse<S: Stage>(&mut self, cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) {
self.0.parse(cx, args)
}
}

View file

@ -19,7 +19,7 @@ impl<S: Stage> SingleAttributeParser<S> for RustcLayoutScalarValidRangeStartPars
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Struct)]);
const TEMPLATE: AttributeTemplate = template!(List: &["start"]);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
parse_single_integer(cx, args)
.map(|n| AttributeKind::RustcLayoutScalarValidRangeStart(Box::new(n), cx.attr_span))
}
@ -34,7 +34,7 @@ impl<S: Stage> SingleAttributeParser<S> for RustcLayoutScalarValidRangeEndParser
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Struct)]);
const TEMPLATE: AttributeTemplate = template!(List: &["end"]);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
parse_single_integer(cx, args)
.map(|n| AttributeKind::RustcLayoutScalarValidRangeEnd(Box::new(n), cx.attr_span))
}
@ -49,7 +49,7 @@ impl<S: Stage> SingleAttributeParser<S> for RustcObjectLifetimeDefaultParser {
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Struct)]);
const TEMPLATE: AttributeTemplate = template!(Word);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
if let Err(span) = args.no_args() {
cx.expected_no_args(span);
return None;
@ -68,7 +68,7 @@ impl<S: Stage> SingleAttributeParser<S> for RustcSimdMonomorphizeLaneLimitParser
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Struct)]);
const TEMPLATE: AttributeTemplate = template!(NameValueStr: "N");
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let ArgParser::NameValue(nv) = args else {
cx.expected_name_value(cx.attr_span, None);
return None;

View file

@ -267,7 +267,7 @@ impl<S: Stage> AttributeParser<S> for ConstStabilityParser {
/// `name = value`
fn insert_value_into_option_or_error<S: Stage>(
cx: &AcceptContext<'_, '_, S>,
param: &MetaItemParser<'_>,
param: &MetaItemParser,
item: &mut Option<Symbol>,
name: Ident,
) -> Option<()> {
@ -289,7 +289,7 @@ fn insert_value_into_option_or_error<S: Stage>(
/// its stability information.
pub(crate) fn parse_stability<S: Stage>(
cx: &AcceptContext<'_, '_, S>,
args: &ArgParser<'_>,
args: &ArgParser,
) -> Option<(Symbol, StabilityLevel)> {
let mut feature = None;
let mut since = None;
@ -365,7 +365,7 @@ pub(crate) fn parse_stability<S: Stage>(
/// attribute, and return the feature name and its stability information.
pub(crate) fn parse_unstability<S: Stage>(
cx: &AcceptContext<'_, '_, S>,
args: &ArgParser<'_>,
args: &ArgParser,
) -> Option<(Symbol, StabilityLevel)> {
let mut feature = None;
let mut reason = None;

View file

@ -15,7 +15,7 @@ impl<S: Stage> SingleAttributeParser<S> for IgnoreParser {
"https://doc.rust-lang.org/reference/attributes/testing.html#the-ignore-attribute"
);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
Some(AttributeKind::Ignore {
span: cx.attr_span,
reason: match args {
@ -49,7 +49,7 @@ impl<S: Stage> SingleAttributeParser<S> for ShouldPanicParser {
"https://doc.rust-lang.org/reference/attributes/testing.html#the-should_panic-attribute"
);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
Some(AttributeKind::ShouldPanic {
span: cx.attr_span,
reason: match args {

View file

@ -18,7 +18,7 @@ impl<S: Stage> SingleAttributeParser<S> for SkipDuringMethodDispatchParser {
const TEMPLATE: AttributeTemplate = template!(List: &["array, boxed_slice"]);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let mut array = false;
let mut boxed_slice = false;
let Some(args) = args.list() else {

View file

@ -17,7 +17,7 @@ impl<S: Stage> SingleAttributeParser<S> for TransparencyParser {
const TEMPLATE: AttributeTemplate =
template!(NameValueStr: ["transparent", "semitransparent", "opaque"]);
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> {
let Some(nv) = args.name_value() else {
cx.expected_name_value(cx.attr_span, None);
return None;

View file

@ -5,7 +5,7 @@ use rustc_ast::attr::AttributeExt;
use rustc_feature::is_builtin_attr_name;
use rustc_hir::RustcVersion;
use rustc_hir::limit::Limit;
use rustc_span::{Symbol, sym};
use rustc_span::Symbol;
use crate::context::{AcceptContext, Stage};
use crate::parser::{ArgParser, NameValueParser};
@ -32,36 +32,6 @@ pub fn is_builtin_attr(attr: &impl AttributeExt) -> bool {
|| attr.ident().is_some_and(|ident| is_builtin_attr_name(ident.name))
}
pub fn is_doc_alias_attrs_contain_symbol<'tcx, T: AttributeExt + 'tcx>(
attrs: impl Iterator<Item = &'tcx T>,
symbol: Symbol,
) -> bool {
let doc_attrs = attrs.filter(|attr| attr.has_name(sym::doc));
for attr in doc_attrs {
let Some(values) = attr.meta_item_list() else {
continue;
};
let alias_values = values.iter().filter(|v| v.has_name(sym::alias));
for v in alias_values {
if let Some(nested) = v.meta_item_list() {
// #[doc(alias("foo", "bar"))]
let mut iter = nested.iter().filter_map(|item| item.lit()).map(|item| item.symbol);
if iter.any(|s| s == symbol) {
return true;
}
} else if let Some(meta) = v.meta_item()
&& let Some(lit) = meta.name_value_literal()
{
// #[doc(alias = "foo")]
if lit.symbol == symbol {
return true;
}
}
}
}
false
}
/// Parse a single integer.
///
/// Used by attributes that take a single integer as argument, such as
@ -70,7 +40,7 @@ pub fn is_doc_alias_attrs_contain_symbol<'tcx, T: AttributeExt + 'tcx>(
/// `args` is the parser for the attribute arguments.
pub(crate) fn parse_single_integer<S: Stage>(
cx: &mut AcceptContext<'_, '_, S>,
args: &ArgParser<'_>,
args: &ArgParser,
) -> Option<u128> {
let Some(list) = args.list() else {
cx.expected_list(cx.attr_span);

View file

@ -33,6 +33,7 @@ use crate::attributes::crate_level::{
};
use crate::attributes::debugger::DebuggerViualizerParser;
use crate::attributes::deprecation::DeprecationParser;
use crate::attributes::doc::DocParser;
use crate::attributes::dummy::DummyParser;
use crate::attributes::inline::{InlineParser, RustcForceInlineParser};
use crate::attributes::link_attrs::{
@ -74,7 +75,7 @@ use crate::attributes::traits::{
};
use crate::attributes::transparency::TransparencyParser;
use crate::attributes::{AttributeParser as _, Combine, Single, WithoutArgs};
use crate::parser::{ArgParser, PathParser};
use crate::parser::{ArgParser, RefPathParser};
use crate::session_diagnostics::{
AttributeParseError, AttributeParseErrorReason, ParsedDescription, UnknownMetaItem,
};
@ -94,7 +95,7 @@ pub(super) struct GroupTypeInnerAccept<S: Stage> {
}
type AcceptFn<S> =
Box<dyn for<'sess, 'a> Fn(&mut AcceptContext<'_, 'sess, S>, &ArgParser<'a>) + Send + Sync>;
Box<dyn for<'sess, 'a> Fn(&mut AcceptContext<'_, 'sess, S>, &ArgParser) + Send + Sync>;
type FinalizeFn<S> =
Box<dyn Send + Sync + Fn(&mut FinalizeContext<'_, '_, S>) -> Option<AttributeKind>>;
@ -162,6 +163,7 @@ attribute_parsers!(
BodyStabilityParser,
ConfusablesParser,
ConstStabilityParser,
DocParser,
MacroUseParser,
NakedParser,
StabilityParser,
@ -427,7 +429,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> {
&self,
span: Span,
found: String,
options: &'static [&'static str],
options: &[&'static str],
) -> ErrorGuaranteed {
self.emit_err(UnknownMetaItem { span, item: found, expected: options })
}
@ -711,7 +713,7 @@ pub(crate) struct FinalizeContext<'p, 'sess, S: Stage> {
///
/// Usually, you should use normal attribute parsing logic instead,
/// especially when making a *denylist* of other attributes.
pub(crate) all_attrs: &'p [PathParser<'p>],
pub(crate) all_attrs: &'p [RefPathParser<'p>],
}
impl<'p, 'sess: 'p, S: Stage> Deref for FinalizeContext<'p, 'sess, S> {

View file

@ -1,6 +1,7 @@
use std::borrow::Cow;
use std::convert::identity;
use rustc_ast as ast;
use rustc_ast::token::DocFragmentKind;
use rustc_ast::{AttrStyle, NodeId, Safety};
use rustc_errors::DiagCtxtHandle;
use rustc_feature::{AttributeTemplate, Features};
@ -12,7 +13,7 @@ use rustc_session::lint::BuiltinLintDiag;
use rustc_span::{DUMMY_SP, Span, Symbol, sym};
use crate::context::{AcceptContext, FinalizeContext, SharedContext, Stage};
use crate::parser::{ArgParser, MetaItemParser, PathParser};
use crate::parser::{ArgParser, PathParser, RefPathParser};
use crate::session_diagnostics::ParsedDescription;
use crate::{Early, Late, OmitDoc, ShouldEmit};
@ -135,7 +136,7 @@ impl<'sess> AttributeParser<'sess, Early> {
target_node_id: NodeId,
features: Option<&'sess Features>,
emit_errors: ShouldEmit,
parse_fn: fn(cx: &mut AcceptContext<'_, '_, Early>, item: &ArgParser<'_>) -> Option<T>,
parse_fn: fn(cx: &mut AcceptContext<'_, '_, Early>, item: &ArgParser) -> Option<T>,
template: &AttributeTemplate,
) -> Option<T> {
let ast::AttrKind::Normal(normal_attr) = &attr.kind else {
@ -143,22 +144,23 @@ impl<'sess> AttributeParser<'sess, Early> {
};
let parts =
normal_attr.item.path.segments.iter().map(|seg| seg.ident.name).collect::<Vec<_>>();
let meta_parser = MetaItemParser::from_attr(normal_attr, &parts, &sess.psess, emit_errors)?;
let path = meta_parser.path();
let args = meta_parser.args();
let path = AttrPath::from_ast(&normal_attr.item.path, identity);
let args =
ArgParser::from_attr_args(&normal_attr.item.args, &parts, &sess.psess, emit_errors)?;
Self::parse_single_args(
sess,
attr.span,
normal_attr.item.span(),
attr.style,
path.get_attribute_path(),
path,
Some(normal_attr.item.unsafety),
ParsedDescription::Attribute,
target_span,
target_node_id,
features,
emit_errors,
args,
&args,
parse_fn,
template,
)
@ -266,7 +268,7 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
mut emit_lint: impl FnMut(AttributeLint<S::Id>),
) -> Vec<Attribute> {
let mut attributes = Vec::new();
let mut attr_paths = Vec::new();
let mut attr_paths: Vec<RefPathParser<'_>> = Vec::new();
for attr in attrs {
// If we're only looking for a single attribute, skip all the ones we don't care about.
@ -281,7 +283,8 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
// that's expanded right? But no, sometimes, when parsing attributes on macros,
// we already use the lowering logic and these are still there. So, when `omit_doc`
// is set we *also* want to ignore these.
if omit_doc == OmitDoc::Skip && attr.has_name(sym::doc) {
let is_doc_attribute = attr.has_name(sym::doc);
if omit_doc == OmitDoc::Skip && is_doc_attribute {
continue;
}
@ -293,24 +296,13 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
attributes.push(Attribute::Parsed(AttributeKind::DocComment {
style: attr.style,
kind: *comment_kind,
kind: DocFragmentKind::Sugared(*comment_kind),
span: lower_span(attr.span),
comment: *symbol,
}))
}
// // FIXME: make doc attributes go through a proper attribute parser
// ast::AttrKind::Normal(n) if n.has_name(sym::doc) => {
// let p = GenericMetaItemParser::from_attr(&n, self.dcx());
//
// attributes.push(Attribute::Parsed(AttributeKind::DocComment {
// style: attr.style,
// kind: CommentKind::Line,
// span: attr.span,
// comment: p.args().name_value(),
// }))
// }
ast::AttrKind::Normal(n) => {
attr_paths.push(PathParser(Cow::Borrowed(&n.item.path)));
attr_paths.push(PathParser(&n.item.path));
let attr_path = AttrPath::from_ast(&n.item.path, lower_span);
self.check_attribute_safety(
@ -325,15 +317,46 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
n.item.path.segments.iter().map(|seg| seg.ident.name).collect::<Vec<_>>();
if let Some(accepts) = S::parsers().accepters.get(parts.as_slice()) {
let Some(parser) = MetaItemParser::from_attr(
n,
let Some(args) = ArgParser::from_attr_args(
&n.item.args,
&parts,
&self.sess.psess,
self.stage.should_emit(),
) else {
continue;
};
let args = parser.args();
// Special-case handling for `#[doc = "..."]`: if we go through with
// `DocParser`, the order of doc comments will be messed up because `///`
// doc comments are added into `attributes` whereas attributes parsed with
// `DocParser` are added into `parsed_attributes` which are then appended
// to `attributes`. So if you have:
//
// /// bla
// #[doc = "a"]
// /// blob
//
// You would get:
//
// bla
// blob
// a
if is_doc_attribute
&& let ArgParser::NameValue(nv) = &args
// If not a string key/value, it should emit an error, but to make
// things simpler, it's handled in `DocParser` because it's simpler to
// emit an error with `AcceptContext`.
&& let Some(comment) = nv.value_as_str()
{
attributes.push(Attribute::Parsed(AttributeKind::DocComment {
style: attr.style,
kind: DocFragmentKind::Raw(nv.value_span),
span: attr.span,
comment,
}));
continue;
}
for accept in accepts {
let mut cx: AcceptContext<'_, 'sess, S> = AcceptContext {
shared: SharedContext {
@ -350,7 +373,7 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
attr_path: attr_path.clone(),
};
(accept.accept_fn)(&mut cx, args);
(accept.accept_fn)(&mut cx, &args);
if !matches!(cx.stage.should_emit(), ShouldEmit::Nothing) {
Self::check_target(&accept.allowed_targets, target, &mut cx);
}

View file

@ -78,6 +78,8 @@
// tidy-alphabetical-start
#![feature(decl_macro)]
#![feature(if_let_guard)]
#![feature(iter_intersperse)]
#![recursion_limit = "256"]
// tidy-alphabetical-end
@ -107,7 +109,7 @@ pub use attributes::cfg::{
};
pub use attributes::cfg_old::*;
pub use attributes::cfg_select::*;
pub use attributes::util::{is_builtin_attr, is_doc_alias_attrs_contain_symbol, parse_version};
pub use attributes::util::{is_builtin_attr, parse_version};
pub use context::{Early, Late, OmitDoc, ShouldEmit};
pub use interface::AttributeParser;
pub use session_diagnostics::ParsedDescription;

View file

@ -3,12 +3,12 @@
//!
//! FIXME(jdonszelmann): delete `rustc_ast/attr/mod.rs`
use std::borrow::Cow;
use std::borrow::Borrow;
use std::fmt::{Debug, Display};
use rustc_ast::token::{self, Delimiter, MetaVarKind};
use rustc_ast::tokenstream::TokenStream;
use rustc_ast::{AttrArgs, Expr, ExprKind, LitKind, MetaItemLit, NormalAttr, Path, StmtKind, UnOp};
use rustc_ast::{AttrArgs, Expr, ExprKind, LitKind, MetaItemLit, Path, StmtKind, UnOp};
use rustc_ast_pretty::pprust;
use rustc_errors::{Diag, PResult};
use rustc_hir::{self as hir, AttrPath};
@ -26,9 +26,12 @@ use crate::session_diagnostics::{
};
#[derive(Clone, Debug)]
pub struct PathParser<'a>(pub Cow<'a, Path>);
pub struct PathParser<P: Borrow<Path>>(pub P);
impl<'a> PathParser<'a> {
pub type OwnedPathParser = PathParser<Path>;
pub type RefPathParser<'p> = PathParser<&'p Path>;
impl<P: Borrow<Path>> PathParser<P> {
pub fn get_attribute_path(&self) -> hir::AttrPath {
AttrPath {
segments: self.segments().copied().collect::<Vec<_>>().into_boxed_slice(),
@ -36,16 +39,16 @@ impl<'a> PathParser<'a> {
}
}
pub fn segments(&'a self) -> impl Iterator<Item = &'a Ident> {
self.0.segments.iter().map(|seg| &seg.ident)
pub fn segments(&self) -> impl Iterator<Item = &Ident> {
self.0.borrow().segments.iter().map(|seg| &seg.ident)
}
pub fn span(&self) -> Span {
self.0.span
self.0.borrow().span
}
pub fn len(&self) -> usize {
self.0.segments.len()
self.0.borrow().segments.len()
}
pub fn segments_is(&self, segments: &[Symbol]) -> bool {
@ -76,21 +79,21 @@ impl<'a> PathParser<'a> {
}
}
impl Display for PathParser<'_> {
impl<P: Borrow<Path>> Display for PathParser<P> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", pprust::path_to_string(&self.0))
write!(f, "{}", pprust::path_to_string(self.0.borrow()))
}
}
#[derive(Clone, Debug)]
#[must_use]
pub enum ArgParser<'a> {
pub enum ArgParser {
NoArgs,
List(MetaItemListParser<'a>),
List(MetaItemListParser),
NameValue(NameValueParser),
}
impl<'a> ArgParser<'a> {
impl ArgParser {
pub fn span(&self) -> Option<Span> {
match self {
Self::NoArgs => None,
@ -100,7 +103,7 @@ impl<'a> ArgParser<'a> {
}
pub fn from_attr_args<'sess>(
value: &'a AttrArgs,
value: &AttrArgs,
parts: &[Symbol],
psess: &'sess ParseSess,
should_emit: ShouldEmit,
@ -144,7 +147,7 @@ impl<'a> ArgParser<'a> {
///
/// - `#[allow(clippy::complexity)]`: `(clippy::complexity)` is a list
/// - `#[rustfmt::skip::macros(target_macro_name)]`: `(target_macro_name)` is a list
pub fn list(&self) -> Option<&MetaItemListParser<'a>> {
pub fn list(&self) -> Option<&MetaItemListParser> {
match self {
Self::List(l) => Some(l),
Self::NameValue(_) | Self::NoArgs => None,
@ -184,17 +187,17 @@ impl<'a> ArgParser<'a> {
///
/// Choose which one you want using the provided methods.
#[derive(Debug, Clone)]
pub enum MetaItemOrLitParser<'a> {
MetaItemParser(MetaItemParser<'a>),
pub enum MetaItemOrLitParser {
MetaItemParser(MetaItemParser),
Lit(MetaItemLit),
Err(Span, ErrorGuaranteed),
}
impl<'sess> MetaItemOrLitParser<'sess> {
pub fn parse_single(
impl MetaItemOrLitParser {
pub fn parse_single<'sess>(
parser: &mut Parser<'sess>,
should_emit: ShouldEmit,
) -> PResult<'sess, MetaItemOrLitParser<'static>> {
) -> PResult<'sess, MetaItemOrLitParser> {
let mut this = MetaItemListParserContext { parser, should_emit };
this.parse_meta_item_inner()
}
@ -216,7 +219,7 @@ impl<'sess> MetaItemOrLitParser<'sess> {
}
}
pub fn meta_item(&self) -> Option<&MetaItemParser<'sess>> {
pub fn meta_item(&self) -> Option<&MetaItemParser> {
match self {
MetaItemOrLitParser::MetaItemParser(parser) => Some(parser),
_ => None,
@ -238,12 +241,12 @@ impl<'sess> MetaItemOrLitParser<'sess> {
///
/// The syntax of MetaItems can be found at <https://doc.rust-lang.org/reference/attributes.html>
#[derive(Clone)]
pub struct MetaItemParser<'a> {
path: PathParser<'a>,
args: ArgParser<'a>,
pub struct MetaItemParser {
path: OwnedPathParser,
args: ArgParser,
}
impl<'a> Debug for MetaItemParser<'a> {
impl Debug for MetaItemParser {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MetaItemParser")
.field("path", &self.path)
@ -252,28 +255,12 @@ impl<'a> Debug for MetaItemParser<'a> {
}
}
impl<'a> MetaItemParser<'a> {
/// Create a new parser from a [`NormalAttr`], which is stored inside of any
/// [`ast::Attribute`](rustc_ast::Attribute)
pub fn from_attr<'sess>(
attr: &'a NormalAttr,
parts: &[Symbol],
psess: &'sess ParseSess,
should_emit: ShouldEmit,
) -> Option<Self> {
Some(Self {
path: PathParser(Cow::Borrowed(&attr.item.path)),
args: ArgParser::from_attr_args(&attr.item.args, parts, psess, should_emit)?,
})
}
}
impl<'a> MetaItemParser<'a> {
impl MetaItemParser {
pub fn span(&self) -> Span {
if let Some(other) = self.args.span() {
self.path.span().with_hi(other.hi())
self.path.borrow().span().with_hi(other.hi())
} else {
self.path.span()
self.path.borrow().span()
}
}
@ -282,12 +269,12 @@ impl<'a> MetaItemParser<'a> {
/// - `#[rustfmt::skip]`: `rustfmt::skip` is a path
/// - `#[allow(clippy::complexity)]`: `clippy::complexity` is a path
/// - `#[inline]`: `inline` is a single segment path
pub fn path(&self) -> &PathParser<'a> {
pub fn path(&self) -> &OwnedPathParser {
&self.path
}
/// Gets just the args parser, without caring about the path.
pub fn args(&self) -> &ArgParser<'a> {
pub fn args(&self) -> &ArgParser {
&self.args
}
@ -297,7 +284,7 @@ impl<'a> MetaItemParser<'a> {
/// - `#[inline]`: `inline` is a word
/// - `#[rustfmt::skip]`: `rustfmt::skip` is a path,
/// and not a word and should instead be parsed using [`path`](Self::path)
pub fn word_is(&self, sym: Symbol) -> Option<&ArgParser<'a>> {
pub fn word_is(&self, sym: Symbol) -> Option<&ArgParser> {
self.path().word_is(sym).then(|| self.args())
}
}
@ -421,7 +408,7 @@ impl<'a, 'sess> MetaItemListParserContext<'a, 'sess> {
Ok(lit)
}
fn parse_attr_item(&mut self) -> PResult<'sess, MetaItemParser<'static>> {
fn parse_attr_item(&mut self) -> PResult<'sess, MetaItemParser> {
if let Some(MetaVarKind::Meta { has_meta_form }) = self.parser.token.is_metavar_seq() {
return if has_meta_form {
let attr_item = self
@ -457,10 +444,10 @@ impl<'a, 'sess> MetaItemListParserContext<'a, 'sess> {
ArgParser::NoArgs
};
Ok(MetaItemParser { path: PathParser(Cow::Owned(path)), args })
Ok(MetaItemParser { path: PathParser(path), args })
}
fn parse_meta_item_inner(&mut self) -> PResult<'sess, MetaItemOrLitParser<'static>> {
fn parse_meta_item_inner(&mut self) -> PResult<'sess, MetaItemOrLitParser> {
if let Some(token_lit) = self.parser.eat_token_lit() {
// If a literal token is parsed, we commit to parsing a MetaItemLit for better errors
Ok(MetaItemOrLitParser::Lit(self.unsuffixed_meta_item_from_lit(token_lit)?))
@ -547,7 +534,7 @@ impl<'a, 'sess> MetaItemListParserContext<'a, 'sess> {
psess: &'sess ParseSess,
span: Span,
should_emit: ShouldEmit,
) -> PResult<'sess, MetaItemListParser<'static>> {
) -> PResult<'sess, MetaItemListParser> {
let mut parser = Parser::new(psess, tokens, None);
let mut this = MetaItemListParserContext { parser: &mut parser, should_emit };
@ -570,14 +557,14 @@ impl<'a, 'sess> MetaItemListParserContext<'a, 'sess> {
}
#[derive(Debug, Clone)]
pub struct MetaItemListParser<'a> {
sub_parsers: ThinVec<MetaItemOrLitParser<'a>>,
pub struct MetaItemListParser {
sub_parsers: ThinVec<MetaItemOrLitParser>,
pub span: Span,
}
impl<'a> MetaItemListParser<'a> {
impl MetaItemListParser {
pub(crate) fn new<'sess>(
tokens: &'a TokenStream,
tokens: &TokenStream,
span: Span,
psess: &'sess ParseSess,
should_emit: ShouldEmit,
@ -586,7 +573,7 @@ impl<'a> MetaItemListParser<'a> {
}
/// Lets you pick and choose as what you want to parse each element in the list
pub fn mixed(&self) -> impl Iterator<Item = &MetaItemOrLitParser<'a>> {
pub fn mixed(&self) -> impl Iterator<Item = &MetaItemOrLitParser> {
self.sub_parsers.iter()
}
@ -601,7 +588,7 @@ impl<'a> MetaItemListParser<'a> {
/// Returns Some if the list contains only a single element.
///
/// Inside the Some is the parser to parse this single element.
pub fn single(&self) -> Option<&MetaItemOrLitParser<'a>> {
pub fn single(&self) -> Option<&MetaItemOrLitParser> {
let mut iter = self.mixed();
iter.next().filter(|_| iter.next().is_none())
}

View file

@ -34,6 +34,49 @@ pub(crate) struct InvalidPredicate {
pub predicate: String,
}
#[derive(Diagnostic)]
#[diag(attr_parsing_doc_alias_empty)]
pub(crate) struct DocAliasEmpty<'a> {
#[primary_span]
pub span: Span,
pub attr_str: &'a str,
}
#[derive(Diagnostic)]
#[diag(attr_parsing_doc_alias_bad_char)]
pub(crate) struct DocAliasBadChar<'a> {
#[primary_span]
pub span: Span,
pub attr_str: &'a str,
pub char_: char,
}
#[derive(Diagnostic)]
#[diag(attr_parsing_doc_alias_start_end)]
pub(crate) struct DocAliasStartEnd<'a> {
#[primary_span]
pub span: Span,
pub attr_str: &'a str,
}
#[derive(Diagnostic)]
#[diag(attr_parsing_doc_keyword_not_keyword)]
#[help]
pub(crate) struct DocKeywordNotKeyword {
#[primary_span]
pub span: Span,
pub keyword: Symbol,
}
#[derive(Diagnostic)]
#[diag(attr_parsing_doc_attribute_not_attribute)]
#[help]
pub(crate) struct DocAttributeNotAttribute {
#[primary_span]
pub span: Span,
pub attribute: Symbol,
}
/// Error code: E0541
pub(crate) struct UnknownMetaItem<'a> {
pub span: Span,
@ -726,16 +769,20 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AttributeParseError<'_> {
diag.note(format!("for more information, visit <{link}>"));
}
diag.span_suggestions(
self.attr_span,
if self.suggestions.len() == 1 {
"must be of the form".to_string()
} else {
format!("try changing it to one of the following valid forms of the {description}")
},
self.suggestions,
Applicability::HasPlaceholders,
);
if self.suggestions.len() < 4 {
diag.span_suggestions(
self.attr_span,
if self.suggestions.len() == 1 {
"must be of the form".to_string()
} else {
format!(
"try changing it to one of the following valid forms of the {description}"
)
},
self.suggestions,
Applicability::HasPlaceholders,
);
}
diag
}
@ -944,3 +991,10 @@ pub(crate) struct CfgAttrBadDelim {
#[subdiagnostic]
pub sugg: MetaBadDelimSugg,
}
#[derive(Diagnostic)]
#[diag(attr_parsing_doc_alias_malformed)]
pub(crate) struct DocAliasMalformed {
#[primary_span]
pub span: Span,
}

View file

@ -198,6 +198,11 @@ fn emit_malformed_attribute(
suggestions.push(format!("#{inner}[{name} = \"{descr}\"]"));
}
}
// If there are too many suggestions, better remove all of them as it's just noise at this
// point.
if suggestions.len() > 3 {
suggestions.clear();
}
if should_warn(name) {
psess.buffer_lint(
ILL_FORMED_ATTRIBUTE_INPUT,

View file

@ -4521,7 +4521,9 @@ struct BreakFinder {
impl<'hir> Visitor<'hir> for BreakFinder {
fn visit_expr(&mut self, ex: &'hir hir::Expr<'hir>) {
match ex.kind {
hir::ExprKind::Break(destination, _) => {
hir::ExprKind::Break(destination, _)
if !ex.span.is_desugaring(DesugaringKind::ForLoop) =>
{
self.found_breaks.push((destination, ex.span));
}
hir::ExprKind::Continue(destination) => {

View file

@ -17,7 +17,7 @@ mod llvm_enzyme {
use rustc_ast::{
self as ast, AngleBracketedArg, AngleBracketedArgs, AnonConst, AssocItemKind, BindingMode,
FnRetTy, FnSig, GenericArg, GenericArgs, GenericParamKind, Generics, ItemKind,
MetaItemInner, PatKind, Path, PathSegment, TyKind, Visibility,
MetaItemInner, MgcaDisambiguation, PatKind, Path, PathSegment, TyKind, Visibility,
};
use rustc_expand::base::{Annotatable, ExtCtxt};
use rustc_span::{Ident, Span, Symbol, sym};
@ -558,7 +558,11 @@ mod llvm_enzyme {
}
GenericParamKind::Const { .. } => {
let expr = ecx.expr_path(ast::Path::from_ident(p.ident));
let anon_const = AnonConst { id: ast::DUMMY_NODE_ID, value: expr };
let anon_const = AnonConst {
id: ast::DUMMY_NODE_ID,
value: expr,
mgca_disambiguation: MgcaDisambiguation::Direct,
};
Some(AngleBracketedArg::Arg(GenericArg::Const(anon_const)))
}
GenericParamKind::Lifetime { .. } => None,
@ -813,6 +817,7 @@ mod llvm_enzyme {
let anon_const = rustc_ast::AnonConst {
id: ast::DUMMY_NODE_ID,
value: ecx.expr_usize(span, 1 + x.width as usize),
mgca_disambiguation: MgcaDisambiguation::Direct,
};
TyKind::Array(ty.clone(), anon_const)
};
@ -827,6 +832,7 @@ mod llvm_enzyme {
let anon_const = rustc_ast::AnonConst {
id: ast::DUMMY_NODE_ID,
value: ecx.expr_usize(span, x.width as usize),
mgca_disambiguation: MgcaDisambiguation::Direct,
};
let kind = TyKind::Array(ty.clone(), anon_const);
let ty =

View file

@ -1,5 +1,5 @@
use rustc_ast::tokenstream::TokenStream;
use rustc_ast::{AnonConst, DUMMY_NODE_ID, Ty, TyPat, TyPatKind, ast, token};
use rustc_ast::{AnonConst, DUMMY_NODE_ID, MgcaDisambiguation, Ty, TyPat, TyPatKind, ast, token};
use rustc_errors::PResult;
use rustc_expand::base::{self, DummyResult, ExpandResult, ExtCtxt, MacroExpanderResult};
use rustc_parse::exp;
@ -60,8 +60,20 @@ fn ty_pat(kind: TyPatKind, span: Span) -> TyPat {
fn pat_to_ty_pat(cx: &mut ExtCtxt<'_>, pat: ast::Pat) -> TyPat {
let kind = match pat.kind {
ast::PatKind::Range(start, end, include_end) => TyPatKind::Range(
start.map(|value| Box::new(AnonConst { id: DUMMY_NODE_ID, value })),
end.map(|value| Box::new(AnonConst { id: DUMMY_NODE_ID, value })),
start.map(|value| {
Box::new(AnonConst {
id: DUMMY_NODE_ID,
value,
mgca_disambiguation: MgcaDisambiguation::Direct,
})
}),
end.map(|value| {
Box::new(AnonConst {
id: DUMMY_NODE_ID,
value,
mgca_disambiguation: MgcaDisambiguation::Direct,
})
}),
include_end,
),
ast::PatKind::Or(variants) => {

View file

@ -167,7 +167,7 @@ pub(crate) fn compile_fn(
context.clear();
context.func = codegened_func.func;
#[cfg(any())] // This is never true
#[cfg(false)]
let _clif_guard = {
use std::fmt::Write;

View file

@ -1,4 +1,4 @@
#### this error code is no longer emitted by the compiler.
#### Note: this error code is no longer emitted by the compiler.
This was triggered when multiple macro definitions used the same
`#[rustc_builtin_macro(..)]`. This is no longer an error.

File diff suppressed because it is too large Load diff

View file

@ -23,15 +23,15 @@ impl fmt::Display for ErrCode {
rustc_error_messages::into_diag_arg_using_display!(ErrCode);
macro_rules! define_error_code_constants_and_diagnostics_table {
($($name:ident: $num:literal,)*) => (
($($num:literal,)*) => (
$(
pub const $name: $crate::ErrCode = $crate::ErrCode::from_u32($num);
pub const ${concat(E, $num)}: $crate::ErrCode = $crate::ErrCode::from_u32($num);
)*
pub static DIAGNOSTICS: &[($crate::ErrCode, &str)] = &[
$( (
$name,
${concat(E, $num)},
include_str!(
concat!("../../rustc_error_codes/src/error_codes/", stringify!($name), ".md")
concat!("../../rustc_error_codes/src/error_codes/E", stringify!($num), ".md")
)
), )*
];

View file

@ -474,9 +474,12 @@ pub trait Emitter {
.chain(span.span_labels().iter().map(|sp_label| sp_label.span))
.filter_map(|sp| {
if !sp.is_dummy() && source_map.is_imported(sp) {
let maybe_callsite = sp.source_callsite();
if sp != maybe_callsite {
return Some((sp, maybe_callsite));
let mut span = sp;
while let Some(callsite) = span.parent_callsite() {
span = callsite;
if !source_map.is_imported(span) {
return Some((sp, span));
}
}
}
None

View file

@ -13,6 +13,7 @@
#![feature(box_patterns)]
#![feature(default_field_values)]
#![feature(error_reporter)]
#![feature(macro_metavar_expr_concat)]
#![feature(negative_impls)]
#![feature(never_type)]
#![feature(rustc_attrs)]

View file

@ -2,8 +2,8 @@ use rustc_ast::token::Delimiter;
use rustc_ast::tokenstream::TokenStream;
use rustc_ast::util::literal;
use rustc_ast::{
self as ast, AnonConst, AttrItem, AttrVec, BlockCheckMode, Expr, LocalKind, MatchKind, PatKind,
UnOp, attr, token, tokenstream,
self as ast, AnonConst, AttrItem, AttrVec, BlockCheckMode, Expr, LocalKind, MatchKind,
MgcaDisambiguation, PatKind, UnOp, attr, token, tokenstream,
};
use rustc_span::source_map::Spanned;
use rustc_span::{DUMMY_SP, Ident, Span, Symbol, kw, sym};
@ -101,6 +101,7 @@ impl<'a> ExtCtxt<'a> {
attrs: AttrVec::new(),
tokens: None,
}),
mgca_disambiguation: MgcaDisambiguation::Direct,
}
}

View file

@ -2177,7 +2177,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
continue;
}
if attr.is_doc_comment() {
if attr.doc_str_and_fragment_kind().is_some() {
self.cx.sess.psess.buffer_lint(
UNUSED_DOC_COMMENTS,
current_span,

View file

@ -1,10 +1,12 @@
use std::borrow::Cow;
use std::fmt;
use std::path::PathBuf;
pub use ReprAttr::*;
use rustc_abi::Align;
use rustc_ast::token::CommentKind;
use rustc_ast::token::DocFragmentKind;
use rustc_ast::{AttrStyle, ast};
use rustc_data_structures::fx::FxIndexMap;
use rustc_error_messages::{DiagArgValue, IntoDiagArg};
use rustc_macros::{Decodable, Encodable, HashStable_Generic, PrintAttribute};
use rustc_span::def_id::DefId;
@ -196,14 +198,68 @@ pub enum CfgEntry {
impl CfgEntry {
pub fn span(&self) -> Span {
let (CfgEntry::All(_, span)
| CfgEntry::Any(_, span)
| CfgEntry::Not(_, span)
| CfgEntry::Bool(_, span)
| CfgEntry::NameValue { span, .. }
| CfgEntry::Version(_, span)) = self;
let (Self::All(_, span)
| Self::Any(_, span)
| Self::Not(_, span)
| Self::Bool(_, span)
| Self::NameValue { span, .. }
| Self::Version(_, span)) = self;
*span
}
/// Same as `PartialEq` but doesn't check spans and ignore order of cfgs.
pub fn is_equivalent_to(&self, other: &Self) -> bool {
match (self, other) {
(Self::All(a, _), Self::All(b, _)) | (Self::Any(a, _), Self::Any(b, _)) => {
a.len() == b.len() && a.iter().all(|a| b.iter().any(|b| a.is_equivalent_to(b)))
}
(Self::Not(a, _), Self::Not(b, _)) => a.is_equivalent_to(b),
(Self::Bool(a, _), Self::Bool(b, _)) => a == b,
(
Self::NameValue { name: name1, value: value1, .. },
Self::NameValue { name: name2, value: value2, .. },
) => name1 == name2 && value1 == value2,
(Self::Version(a, _), Self::Version(b, _)) => a == b,
_ => false,
}
}
}
impl fmt::Display for CfgEntry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fn write_entries(
name: &str,
entries: &[CfgEntry],
f: &mut fmt::Formatter<'_>,
) -> fmt::Result {
write!(f, "{name}(")?;
for (nb, entry) in entries.iter().enumerate() {
if nb != 0 {
f.write_str(", ")?;
}
entry.fmt(f)?;
}
f.write_str(")")
}
match self {
Self::All(entries, _) => write_entries("all", entries, f),
Self::Any(entries, _) => write_entries("any", entries, f),
Self::Not(entry, _) => write!(f, "not({entry})"),
Self::Bool(value, _) => write!(f, "{value}"),
Self::NameValue { name, value, .. } => {
match value {
// We use `as_str` and debug display to have characters escaped and `"`
// characters surrounding the string.
Some(value) => write!(f, "{name} = {:?}", value.as_str()),
None => write!(f, "{name}"),
}
}
Self::Version(version, _) => match version {
Some(version) => write!(f, "{version}"),
None => Ok(()),
},
}
}
}
/// Possible values for the `#[linkage]` attribute, allowing to specify the
@ -420,6 +476,79 @@ impl WindowsSubsystemKind {
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[derive(HashStable_Generic, Encodable, Decodable, PrintAttribute)]
pub enum DocInline {
Inline,
NoInline,
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[derive(HashStable_Generic, Encodable, Decodable, PrintAttribute)]
pub enum HideOrShow {
Hide,
Show,
}
#[derive(Clone, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute)]
pub struct CfgInfo {
pub name: Symbol,
pub name_span: Span,
pub value: Option<(Symbol, Span)>,
}
impl CfgInfo {
pub fn span_for_name_and_value(&self) -> Span {
if let Some((_, value_span)) = self.value {
self.name_span.with_hi(value_span.hi())
} else {
self.name_span
}
}
}
#[derive(Clone, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute)]
pub struct CfgHideShow {
pub kind: HideOrShow,
pub values: ThinVec<CfgInfo>,
}
#[derive(Clone, Debug, Default, HashStable_Generic, Encodable, Decodable, PrintAttribute)]
pub struct DocAttribute {
pub aliases: FxIndexMap<Symbol, Span>,
pub hidden: Option<Span>,
// Because we need to emit the error if there is more than one `inline` attribute on an item
// at the same time as the other doc attributes, we store a list instead of using `Option`.
pub inline: ThinVec<(DocInline, Span)>,
// unstable
pub cfg: ThinVec<CfgEntry>,
pub auto_cfg: ThinVec<(CfgHideShow, Span)>,
/// This is for `#[doc(auto_cfg = false|true)]`/`#[doc(auto_cfg)]`.
pub auto_cfg_change: ThinVec<(bool, Span)>,
// builtin
pub fake_variadic: Option<Span>,
pub keyword: Option<(Symbol, Span)>,
pub attribute: Option<(Symbol, Span)>,
pub masked: Option<Span>,
pub notable_trait: Option<Span>,
pub search_unbox: Option<Span>,
// valid on crate
pub html_favicon_url: Option<(Symbol, Span)>,
pub html_logo_url: Option<(Symbol, Span)>,
pub html_playground_url: Option<(Symbol, Span)>,
pub html_root_url: Option<(Symbol, Span)>,
pub html_no_source: Option<Span>,
pub issue_tracker_base_url: Option<(Symbol, Span)>,
pub rust_logo: Option<Span>,
// #[doc(test(...))]
pub test_attrs: ThinVec<Span>,
pub no_crate_inject: Option<Span>,
}
/// Represents parsed *built-in* inert attributes.
///
/// ## Overview
@ -551,8 +680,14 @@ pub enum AttributeKind {
/// Represents `#[rustc_do_not_implement_via_object]`.
DoNotImplementViaObject(Span),
/// Represents [`#[doc = "..."]`](https://doc.rust-lang.org/stable/rustdoc/write-documentation/the-doc-attribute.html).
DocComment { style: AttrStyle, kind: CommentKind, span: Span, comment: Symbol },
/// Represents [`#[doc]`](https://doc.rust-lang.org/stable/rustdoc/write-documentation/the-doc-attribute.html).
/// Represents all other uses of the [`#[doc]`](https://doc.rust-lang.org/stable/rustdoc/write-documentation/the-doc-attribute.html)
/// attribute.
Doc(Box<DocAttribute>),
/// Represents specifically [`#[doc = "..."]`](https://doc.rust-lang.org/stable/rustdoc/write-documentation/the-doc-attribute.html).
/// i.e. doc comments.
DocComment { style: AttrStyle, kind: DocFragmentKind, span: Span, comment: Symbol },
/// Represents `#[rustc_dummy]`.
Dummy,

View file

@ -40,6 +40,7 @@ impl AttributeKind {
DenyExplicitImpl(..) => No,
Deprecation { .. } => Yes,
DoNotImplementViaObject(..) => No,
Doc(_) => Yes,
DocComment { .. } => Yes,
Dummy => No,
ExportName { .. } => Yes,

View file

@ -1,9 +1,10 @@
use std::num::NonZero;
use rustc_abi::Align;
use rustc_ast::token::CommentKind;
use rustc_ast::token::{CommentKind, DocFragmentKind};
use rustc_ast::{AttrStyle, IntTy, UintTy};
use rustc_ast_pretty::pp::Printer;
use rustc_data_structures::fx::FxIndexMap;
use rustc_span::hygiene::Transparency;
use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol};
use rustc_target::spec::SanitizerSet;
@ -64,6 +65,25 @@ impl<T: PrintAttribute> PrintAttribute for ThinVec<T> {
p.word("]");
}
}
impl<T: PrintAttribute> PrintAttribute for FxIndexMap<T, Span> {
fn should_render(&self) -> bool {
self.is_empty() || self[0].should_render()
}
fn print_attribute(&self, p: &mut Printer) {
let mut last_printed = false;
p.word("[");
for (i, _) in self {
if last_printed {
p.word_space(",");
}
i.print_attribute(p);
last_printed = i.should_render();
}
p.word("]");
}
}
macro_rules! print_skip {
($($t: ty),* $(,)?) => {$(
impl PrintAttribute for $t {
@ -147,6 +167,7 @@ print_debug!(
Align,
AttrStyle,
CommentKind,
DocFragmentKind,
Transparency,
SanitizerSet,
);

View file

@ -4,7 +4,7 @@ use std::fmt;
use rustc_abi::ExternAbi;
use rustc_ast::attr::AttributeExt;
use rustc_ast::token::CommentKind;
use rustc_ast::token::DocFragmentKind;
use rustc_ast::util::parser::ExprPrecedence;
use rustc_ast::{
self as ast, FloatTy, InlineAsmOptions, InlineAsmTemplatePiece, IntTy, Label, LitIntType,
@ -1376,7 +1376,6 @@ impl AttributeExt for Attribute {
fn doc_str(&self) -> Option<Symbol> {
match &self {
Attribute::Parsed(AttributeKind::DocComment { comment, .. }) => Some(*comment),
Attribute::Unparsed(_) if self.has_name(sym::doc) => self.value_str(),
_ => None,
}
}
@ -1386,14 +1385,11 @@ impl AttributeExt for Attribute {
}
#[inline]
fn doc_str_and_comment_kind(&self) -> Option<(Symbol, CommentKind)> {
fn doc_str_and_fragment_kind(&self) -> Option<(Symbol, DocFragmentKind)> {
match &self {
Attribute::Parsed(AttributeKind::DocComment { kind, comment, .. }) => {
Some((*comment, *kind))
}
Attribute::Unparsed(_) if self.has_name(sym::doc) => {
self.value_str().map(|s| (s, CommentKind::Line))
}
_ => None,
}
}
@ -1418,6 +1414,14 @@ impl AttributeExt for Attribute {
)
)
}
fn is_doc_hidden(&self) -> bool {
matches!(self, Attribute::Parsed(AttributeKind::Doc(d)) if d.hidden.is_some())
}
fn is_doc_keyword_or_attribute(&self) -> bool {
matches!(self, Attribute::Parsed(AttributeKind::Doc(d)) if d.attribute.is_some() || d.keyword.is_some())
}
}
// FIXME(fn_delegation): use function delegation instead of manually forwarding
@ -1503,8 +1507,8 @@ impl Attribute {
}
#[inline]
pub fn doc_str_and_comment_kind(&self) -> Option<(Symbol, CommentKind)> {
AttributeExt::doc_str_and_comment_kind(self)
pub fn doc_str_and_fragment_kind(&self) -> Option<(Symbol, DocFragmentKind)> {
AttributeExt::doc_str_and_fragment_kind(self)
}
}

View file

@ -1147,17 +1147,16 @@ fn check_trait(tcx: TyCtxt<'_>, item: &hir::Item<'_>) -> Result<(), ErrorGuarant
///
/// Assuming the defaults are used, check that all predicates (bounds on the
/// assoc type and where clauses on the trait) hold.
fn check_associated_type_bounds(wfcx: &WfCheckingCtxt<'_, '_>, item: ty::AssocItem, span: Span) {
fn check_associated_type_bounds(wfcx: &WfCheckingCtxt<'_, '_>, item: ty::AssocItem, _span: Span) {
let bounds = wfcx.tcx().explicit_item_bounds(item.def_id);
debug!("check_associated_type_bounds: bounds={:?}", bounds);
let wf_obligations = bounds.iter_identity_copied().flat_map(|(bound, bound_span)| {
let normalized_bound = wfcx.normalize(span, None, bound);
traits::wf::clause_obligations(
wfcx.infcx,
wfcx.param_env,
wfcx.body_def_id,
normalized_bound,
bound,
bound_span,
)
});
@ -1525,7 +1524,6 @@ pub(super) fn check_where_clauses<'tcx>(wfcx: &WfCheckingCtxt<'_, 'tcx>, def_id:
assert_eq!(predicates.predicates.len(), predicates.spans.len());
let wf_obligations = predicates.into_iter().flat_map(|(p, sp)| {
let p = wfcx.normalize(sp, None, p);
traits::wf::clause_obligations(infcx, wfcx.param_env, wfcx.body_def_id, p, sp)
});
let obligations: Vec<_> = wf_obligations.chain(default_obligations).collect();

View file

@ -8,7 +8,6 @@ edition = "2024"
itertools = "0.12"
rustc_abi = { path = "../rustc_abi" }
rustc_ast = { path = "../rustc_ast" }
rustc_attr_parsing = { path = "../rustc_attr_parsing" }
rustc_data_structures = { path = "../rustc_data_structures" }
rustc_errors = { path = "../rustc_errors" }
rustc_fluent_macro = { path = "../rustc_fluent_macro" }

View file

@ -1,6 +1,4 @@
// tidy-alphabetical-start
#![allow(rustc::diagnostic_outside_of_impl)]
#![allow(rustc::untranslatable_diagnostic)]
#![feature(assert_matches)]
#![feature(box_patterns)]
#![feature(if_let_guard)]

View file

@ -3,12 +3,12 @@ use std::cell::{Cell, RefCell};
use std::cmp::max;
use std::ops::Deref;
use rustc_attr_parsing::is_doc_alias_attrs_contain_symbol;
use rustc_data_structures::fx::FxHashSet;
use rustc_data_structures::sso::SsoHashSet;
use rustc_errors::Applicability;
use rustc_hir::attrs::AttributeKind;
use rustc_hir::def::DefKind;
use rustc_hir::{self as hir, ExprKind, HirId, Node};
use rustc_hir::{self as hir, ExprKind, HirId, Node, find_attr};
use rustc_hir_analysis::autoderef::{self, Autoderef};
use rustc_infer::infer::canonical::{Canonical, OriginalQueryValues, QueryResponse};
use rustc_infer::infer::{BoundRegionConversionTime, DefineOpaqueTypes, InferOk, TyCtxtInferExt};
@ -2535,7 +2535,9 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
let hir_id = self.fcx.tcx.local_def_id_to_hir_id(local_def_id);
let attrs = self.fcx.tcx.hir_attrs(hir_id);
if is_doc_alias_attrs_contain_symbol(attrs.into_iter(), method.name) {
if let Some(d) = find_attr!(attrs, AttributeKind::Doc(d) => d)
&& d.aliases.contains_key(&method.name)
{
return true;
}

View file

@ -233,6 +233,58 @@ lint_deprecated_where_clause_location = where clause not allowed here
lint_diag_out_of_impl =
diagnostics should only be created in `Diagnostic`/`Subdiagnostic`/`LintDiagnostic` impls
lint_doc_alias_duplicated = doc alias is duplicated
.label = first defined here
lint_doc_auto_cfg_expects_hide_or_show =
only `hide` or `show` are allowed in `#[doc(auto_cfg(...))]`
lint_doc_auto_cfg_hide_show_expects_list =
`#![doc(auto_cfg({$attr_name}(...)))]` expects a list of items
lint_doc_auto_cfg_hide_show_unexpected_item =
`#![doc(auto_cfg({$attr_name}(...)))]` only accepts identifiers or key/value items
lint_doc_auto_cfg_wrong_literal =
expected boolean for `#[doc(auto_cfg = ...)]`
lint_doc_invalid =
invalid `doc` attribute
lint_doc_test_literal = `#![doc(test(...)]` does not take a literal
lint_doc_test_takes_list =
`#[doc(test(...)]` takes a list of attributes
lint_doc_test_unknown =
unknown `doc(test)` attribute `{$name}`
lint_doc_unknown_any =
unknown `doc` attribute `{$name}`
lint_doc_unknown_include =
unknown `doc` attribute `include`
.suggestion = use `doc = include_str!` instead
lint_doc_unknown_passes =
unknown `doc` attribute `{$name}`
.note = `doc` attribute `{$name}` no longer functions; see issue #44136 <https://github.com/rust-lang/rust/issues/44136>
.label = no longer functions
.no_op_note = `doc({$name})` is now a no-op
lint_doc_unknown_plugins =
unknown `doc` attribute `plugins`
.note = `doc` attribute `plugins` no longer functions; see issue #44136 <https://github.com/rust-lang/rust/issues/44136> and CVE-2018-1000622 <https://nvd.nist.gov/vuln/detail/CVE-2018-1000622>
.label = no longer functions
.no_op_note = `doc(plugins)` is now a no-op
lint_doc_unknown_spotlight =
unknown `doc` attribute `spotlight`
.note = `doc(spotlight)` was renamed to `doc(notable_trait)`
.suggestion = use `notable_trait` instead
.no_op_note = `doc(spotlight)` is now a no-op
lint_drop_glue =
types that do not implement `Drop` can still have drop glue, consider instead using `{$needs_drop}` to detect whether a type is trivially dropped
@ -831,6 +883,7 @@ lint_unexpected_cfg_add_build_rs_println = or consider adding `{$build_rs_printl
lint_unexpected_cfg_add_cargo_feature = consider using a Cargo feature instead
lint_unexpected_cfg_add_cargo_toml_lint_cfg = or consider adding in `Cargo.toml` the `check-cfg` lint config for the lint:{$cargo_toml_lint_cfg}
lint_unexpected_cfg_add_cmdline_arg = to expect this configuration use `{$cmdline_arg}`
lint_unexpected_cfg_boolean = you may have meant to use `{$literal}` (notice the capitalization). Doing so makes this predicate evaluate to `{$literal}` unconditionally
lint_unexpected_cfg_cargo_update = the {$macro_kind} `{$macro_name}` may come from an old version of the `{$crate_name}` crate, try updating your dependency with `cargo update -p {$crate_name}`
lint_unexpected_cfg_define_features = consider defining some features in `Cargo.toml`

View file

@ -25,7 +25,7 @@ use rustc_attr_parsing::AttributeParser;
use rustc_errors::{Applicability, LintDiagnostic};
use rustc_feature::GateIssue;
use rustc_hir as hir;
use rustc_hir::attrs::AttributeKind;
use rustc_hir::attrs::{AttributeKind, DocAttribute};
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::{CRATE_DEF_ID, DefId, LocalDefId};
use rustc_hir::intravisit::FnKind as HirFnKind;
@ -396,26 +396,16 @@ pub struct MissingDoc;
impl_lint_pass!(MissingDoc => [MISSING_DOCS]);
fn has_doc(attr: &hir::Attribute) -> bool {
if attr.is_doc_comment().is_some() {
if matches!(attr, hir::Attribute::Parsed(AttributeKind::DocComment { .. })) {
return true;
}
if !attr.has_name(sym::doc) {
return false;
}
if attr.value_str().is_some() {
if let hir::Attribute::Parsed(AttributeKind::Doc(d)) = attr
&& matches!(d.as_ref(), DocAttribute { hidden: Some(..), .. })
{
return true;
}
if let Some(list) = attr.meta_item_list() {
for meta in list {
if meta.has_name(sym::hidden) {
return true;
}
}
}
false
}
@ -822,19 +812,23 @@ fn warn_if_doc(cx: &EarlyContext<'_>, node_span: Span, node_kind: &str, attrs: &
let mut sugared_span: Option<Span> = None;
while let Some(attr) = attrs.next() {
let is_doc_comment = attr.is_doc_comment();
let (is_doc_comment, is_doc_attribute) = match &attr.kind {
AttrKind::DocComment(..) => (true, false),
AttrKind::Normal(normal) if normal.item.path == sym::doc => (true, true),
_ => (false, false),
};
if is_doc_comment {
sugared_span =
Some(sugared_span.map_or(attr.span, |span| span.with_hi(attr.span.hi())));
}
if attrs.peek().is_some_and(|next_attr| next_attr.is_doc_comment()) {
if !is_doc_attribute && attrs.peek().is_some_and(|next_attr| next_attr.is_doc_comment()) {
continue;
}
let span = sugared_span.take().unwrap_or(attr.span);
if is_doc_comment || attr.has_name(sym::doc) {
if is_doc_comment || is_doc_attribute {
let sub = match attr.kind {
AttrKind::DocComment(CommentKind::Line, _) | AttrKind::Normal(..) => {
BuiltinUnusedDocCommentSub::PlainHelp

View file

@ -367,5 +367,55 @@ pub fn decorate_attribute_lint(
&AttributeLintKind::UnexpectedCfgValue(name, value) => {
check_cfg::unexpected_cfg_value(sess, tcx, name, value).decorate_lint(diag)
}
&AttributeLintKind::DuplicateDocAlias { first_definition } => {
lints::DocAliasDuplicated { first_defn: first_definition }.decorate_lint(diag)
}
&AttributeLintKind::DocAutoCfgExpectsHideOrShow => {
lints::DocAutoCfgExpectsHideOrShow.decorate_lint(diag)
}
&AttributeLintKind::DocAutoCfgHideShowUnexpectedItem { attr_name } => {
lints::DocAutoCfgHideShowUnexpectedItem { attr_name }.decorate_lint(diag)
}
&AttributeLintKind::DocAutoCfgHideShowExpectsList { attr_name } => {
lints::DocAutoCfgHideShowExpectsList { attr_name }.decorate_lint(diag)
}
&AttributeLintKind::DocInvalid => { lints::DocInvalid }.decorate_lint(diag),
&AttributeLintKind::DocUnknownInclude { span, inner, value } => {
lints::DocUnknownInclude { inner, value, sugg: (span, Applicability::MaybeIncorrect) }
}
.decorate_lint(diag),
&AttributeLintKind::DocUnknownSpotlight { span } => {
lints::DocUnknownSpotlight { sugg_span: span }.decorate_lint(diag)
}
&AttributeLintKind::DocUnknownPasses { name, span } => {
lints::DocUnknownPasses { name, note_span: span }.decorate_lint(diag)
}
&AttributeLintKind::DocUnknownPlugins { span } => {
lints::DocUnknownPlugins { label_span: span }.decorate_lint(diag)
}
&AttributeLintKind::DocUnknownAny { name } => {
lints::DocUnknownAny { name }.decorate_lint(diag)
}
&AttributeLintKind::DocAutoCfgWrongLiteral => {
lints::DocAutoCfgWrongLiteral.decorate_lint(diag)
}
&AttributeLintKind::DocTestTakesList => lints::DocTestTakesList.decorate_lint(diag),
&AttributeLintKind::DocTestUnknown { name } => {
lints::DocTestUnknown { name }.decorate_lint(diag)
}
&AttributeLintKind::DocTestLiteral => lints::DocTestLiteral.decorate_lint(diag),
}
}

View file

@ -138,6 +138,16 @@ pub(super) fn unexpected_cfg_name(
let is_from_external_macro = name_span.in_external_macro(sess.source_map());
let mut is_feature_cfg = name == sym::feature;
fn miscapitalized_boolean(name: Symbol) -> Option<bool> {
if name.as_str().eq_ignore_ascii_case("false") {
Some(false)
} else if name.as_str().eq_ignore_ascii_case("true") {
Some(true)
} else {
None
}
}
let code_sugg = if is_feature_cfg && is_from_cargo {
lints::unexpected_cfg_name::CodeSuggestion::DefineFeatures
// Suggest correct `version("..")` predicate syntax
@ -148,6 +158,21 @@ pub(super) fn unexpected_cfg_name(
between_name_and_value: name_span.between(value_span),
after_value: value_span.shrink_to_hi(),
}
// Suggest a literal `false` instead
// Detect miscapitalized `False`/`FALSE` etc, ensuring that this isn't `r#false`
} else if value.is_none()
// If this is a miscapitalized False/FALSE, suggest the boolean literal instead
&& let Some(boolean) = miscapitalized_boolean(name)
// Check this isn't a raw identifier
&& sess
.source_map()
.span_to_snippet(name_span)
.map_or(true, |snippet| !snippet.contains("r#"))
{
lints::unexpected_cfg_name::CodeSuggestion::BooleanLiteral {
span: name_span,
literal: boolean,
}
// Suggest the most probable if we found one
} else if let Some(best_match) = find_best_match_for_name(&possibilities, name, None) {
is_feature_cfg |= best_match == sym::feature;

View file

@ -657,11 +657,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> {
}
// `#[doc(hidden)]` disables missing_docs check.
if attr.has_name(sym::doc)
&& attr
.meta_item_list()
.is_some_and(|l| ast::attr::list_contains_name(&l, sym::hidden))
{
if attr.is_doc_hidden() {
self.insert(
LintId::of(MISSING_DOCS),
LevelAndSource {

View file

@ -2401,6 +2401,17 @@ pub(crate) mod unexpected_cfg_name {
#[subdiagnostic]
expected_names: Option<ExpectedNames>,
},
#[suggestion(
lint_unexpected_cfg_boolean,
applicability = "machine-applicable",
style = "verbose",
code = "{literal}"
)]
BooleanLiteral {
#[primary_span]
span: Span,
literal: bool,
},
}
#[derive(Subdiagnostic)]
@ -3199,3 +3210,91 @@ pub(crate) struct UnusedVisibility {
#[suggestion(style = "short", code = "", applicability = "machine-applicable")]
pub span: Span,
}
#[derive(LintDiagnostic)]
#[diag(lint_doc_alias_duplicated)]
pub(crate) struct DocAliasDuplicated {
#[label]
pub first_defn: Span,
}
#[derive(LintDiagnostic)]
#[diag(lint_doc_auto_cfg_expects_hide_or_show)]
pub(crate) struct DocAutoCfgExpectsHideOrShow;
#[derive(LintDiagnostic)]
#[diag(lint_doc_auto_cfg_hide_show_unexpected_item)]
pub(crate) struct DocAutoCfgHideShowUnexpectedItem {
pub attr_name: Symbol,
}
#[derive(LintDiagnostic)]
#[diag(lint_doc_auto_cfg_hide_show_expects_list)]
pub(crate) struct DocAutoCfgHideShowExpectsList {
pub attr_name: Symbol,
}
#[derive(LintDiagnostic)]
#[diag(lint_doc_invalid)]
pub(crate) struct DocInvalid;
#[derive(LintDiagnostic)]
#[diag(lint_doc_unknown_include)]
pub(crate) struct DocUnknownInclude {
pub inner: &'static str,
pub value: Symbol,
#[suggestion(code = "#{inner}[doc = include_str!(\"{value}\")]")]
pub sugg: (Span, Applicability),
}
#[derive(LintDiagnostic)]
#[diag(lint_doc_unknown_spotlight)]
#[note]
#[note(lint_no_op_note)]
pub(crate) struct DocUnknownSpotlight {
#[suggestion(style = "short", applicability = "machine-applicable", code = "notable_trait")]
pub sugg_span: Span,
}
#[derive(LintDiagnostic)]
#[diag(lint_doc_unknown_passes)]
#[note]
#[note(lint_no_op_note)]
pub(crate) struct DocUnknownPasses {
pub name: Symbol,
#[label]
pub note_span: Span,
}
#[derive(LintDiagnostic)]
#[diag(lint_doc_unknown_plugins)]
#[note]
#[note(lint_no_op_note)]
pub(crate) struct DocUnknownPlugins {
#[label]
pub label_span: Span,
}
#[derive(LintDiagnostic)]
#[diag(lint_doc_unknown_any)]
pub(crate) struct DocUnknownAny {
pub name: Symbol,
}
#[derive(LintDiagnostic)]
#[diag(lint_doc_auto_cfg_wrong_literal)]
pub(crate) struct DocAutoCfgWrongLiteral;
#[derive(LintDiagnostic)]
#[diag(lint_doc_test_takes_list)]
pub(crate) struct DocTestTakesList;
#[derive(LintDiagnostic)]
#[diag(lint_doc_test_unknown)]
pub(crate) struct DocTestUnknown {
pub name: Symbol,
}
#[derive(LintDiagnostic)]
#[diag(lint_doc_test_literal)]
pub(crate) struct DocTestLiteral;

View file

@ -785,6 +785,41 @@ pub enum AttributeLintKind {
},
UnexpectedCfgName((Symbol, Span), Option<(Symbol, Span)>),
UnexpectedCfgValue((Symbol, Span), Option<(Symbol, Span)>),
DuplicateDocAlias {
first_definition: Span,
},
DocAutoCfgExpectsHideOrShow,
DocAutoCfgHideShowUnexpectedItem {
attr_name: Symbol,
},
DocAutoCfgHideShowExpectsList {
attr_name: Symbol,
},
DocInvalid,
DocUnknownInclude {
span: Span,
inner: &'static str,
value: Symbol,
},
DocUnknownSpotlight {
span: Span,
},
DocUnknownPasses {
name: Symbol,
span: Span,
},
DocUnknownPlugins {
span: Span,
},
DocUnknownAny {
name: Symbol,
},
DocAutoCfgWrongLiteral,
DocTestTakesList,
DocTestUnknown {
name: Symbol,
},
DocTestLiteral,
}
pub type RegisteredTools = FxIndexSet<Ident>;

View file

@ -870,25 +870,20 @@ fn analyze_attr(attr: &hir::Attribute, state: &mut AnalyzeAttrState<'_>) -> bool
&& !rustc_feature::encode_cross_crate(name)
{
// Attributes not marked encode-cross-crate don't need to be encoded for downstream crates.
} else if attr.doc_str().is_some() {
} else if let hir::Attribute::Parsed(AttributeKind::DocComment { .. }) = attr {
// We keep all doc comments reachable to rustdoc because they might be "imported" into
// downstream crates if they use `#[doc(inline)]` to copy an item's documentation into
// their own.
if state.is_exported {
should_encode = true;
}
} else if attr.has_name(sym::doc) {
} else if let hir::Attribute::Parsed(AttributeKind::Doc(d)) = attr {
// If this is a `doc` attribute that doesn't have anything except maybe `inline` (as in
// `#[doc(inline)]`), then we can remove it. It won't be inlinable in downstream crates.
if let Some(item_list) = attr.meta_item_list() {
for item in item_list {
if !item.has_name(sym::inline) {
should_encode = true;
if item.has_name(sym::hidden) {
state.is_doc_hidden = true;
break;
}
}
if d.inline.is_empty() {
should_encode = true;
if d.hidden.is_some() {
state.is_doc_hidden = true;
}
}
} else if let &[sym::diagnostic, seg] = &*attr.path() {

View file

@ -4,12 +4,14 @@ use std::{fmt, iter};
use rustc_abi::{Float, Integer, IntegerType, Size};
use rustc_apfloat::Float as _;
use rustc_ast::attr::AttributeExt;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
use rustc_data_structures::stack::ensure_sufficient_stack;
use rustc_errors::ErrorGuaranteed;
use rustc_hashes::Hash128;
use rustc_hir as hir;
use rustc_hir::attrs::AttributeKind;
use rustc_hir::def::{CtorOf, DefKind, Res};
use rustc_hir::def_id::{CrateNum, DefId, LocalDefId};
use rustc_hir::limit::Limit;
@ -1664,16 +1666,14 @@ pub fn reveal_opaque_types_in_bounds<'tcx>(
/// Determines whether an item is directly annotated with `doc(hidden)`.
fn is_doc_hidden(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
tcx.get_attrs(def_id, sym::doc)
.filter_map(|attr| attr.meta_item_list())
.any(|items| items.iter().any(|item| item.has_name(sym::hidden)))
let attrs = tcx.hir_attrs(tcx.local_def_id_to_hir_id(def_id));
attrs.iter().any(|attr| attr.is_doc_hidden())
}
/// Determines whether an item is annotated with `doc(notable_trait)`.
pub fn is_doc_notable_trait(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
tcx.get_attrs(def_id, sym::doc)
.filter_map(|attr| attr.meta_item_list())
.any(|items| items.iter().any(|item| item.has_name(sym::notable_trait)))
let attrs = tcx.get_all_attrs(def_id);
attrs.iter().any(|attr| matches!(attr, hir::Attribute::Parsed(AttributeKind::Doc(doc)) if doc.notable_trait.is_some()))
}
/// Determines whether an item is an intrinsic (which may be via Abi or via the `rustc_intrinsic` attribute).

View file

@ -1,4 +1,4 @@
use rustc_ast::{self as ast, AsmMacro};
use rustc_ast::{self as ast, AsmMacro, MgcaDisambiguation};
use rustc_span::{Span, Symbol, kw};
use super::{ExpKeywordPair, ForceCollect, IdentIsRaw, Trailing, UsePreAttrPos};
@ -149,7 +149,7 @@ fn parse_asm_operand<'a>(
let block = p.parse_block()?;
ast::InlineAsmOperand::Label { block }
} else if p.eat_keyword(exp!(Const)) {
let anon_const = p.parse_expr_anon_const()?;
let anon_const = p.parse_expr_anon_const(|_, _| MgcaDisambiguation::AnonConst)?;
ast::InlineAsmOperand::Const { anon_const }
} else if p.eat_keyword(exp!(Sym)) {
let expr = p.parse_expr()?;

View file

@ -6,8 +6,8 @@ use rustc_ast::token::{self, Lit, LitKind, Token, TokenKind};
use rustc_ast::util::parser::AssocOp;
use rustc_ast::{
self as ast, AngleBracketedArg, AngleBracketedArgs, AnonConst, AttrVec, BinOpKind, BindingMode,
Block, BlockCheckMode, Expr, ExprKind, GenericArg, Generics, Item, ItemKind, Param, Pat,
PatKind, Path, PathSegment, QSelf, Recovered, Ty, TyKind,
Block, BlockCheckMode, Expr, ExprKind, GenericArg, Generics, Item, ItemKind,
MgcaDisambiguation, Param, Pat, PatKind, Path, PathSegment, QSelf, Recovered, Ty, TyKind,
};
use rustc_ast_pretty::pprust;
use rustc_data_structures::fx::FxHashSet;
@ -31,16 +31,15 @@ use crate::errors::{
AddParen, AmbiguousPlus, AsyncMoveBlockIn2015, AsyncUseBlockIn2015, AttributeOnParamType,
AwaitSuggestion, BadQPathStage2, BadTypePlus, BadTypePlusSub, ColonAsSemi,
ComparisonOperatorsCannotBeChained, ComparisonOperatorsCannotBeChainedSugg,
ConstGenericWithoutBraces, ConstGenericWithoutBracesSugg, DocCommentDoesNotDocumentAnything,
DocCommentOnParamType, DoubleColonInBound, ExpectedIdentifier, ExpectedSemi, ExpectedSemiSugg,
GenericParamsWithoutAngleBrackets, GenericParamsWithoutAngleBracketsSugg,
HelpIdentifierStartsWithNumber, HelpUseLatestEdition, InInTypo, IncorrectAwait,
IncorrectSemicolon, IncorrectUseOfAwait, IncorrectUseOfUse, PatternMethodParamWithoutBody,
QuestionMarkInType, QuestionMarkInTypeSugg, SelfParamNotFirst, StructLiteralBodyWithoutPath,
StructLiteralBodyWithoutPathSugg, SuggAddMissingLetStmt, SuggEscapeIdentifier, SuggRemoveComma,
TernaryOperator, TernaryOperatorSuggestion, UnexpectedConstInGenericParam,
UnexpectedConstParamDeclaration, UnexpectedConstParamDeclarationSugg, UnmatchedAngleBrackets,
UseEqInstead, WrapType,
DocCommentDoesNotDocumentAnything, DocCommentOnParamType, DoubleColonInBound,
ExpectedIdentifier, ExpectedSemi, ExpectedSemiSugg, GenericParamsWithoutAngleBrackets,
GenericParamsWithoutAngleBracketsSugg, HelpIdentifierStartsWithNumber, HelpUseLatestEdition,
InInTypo, IncorrectAwait, IncorrectSemicolon, IncorrectUseOfAwait, IncorrectUseOfUse,
PatternMethodParamWithoutBody, QuestionMarkInType, QuestionMarkInTypeSugg, SelfParamNotFirst,
StructLiteralBodyWithoutPath, StructLiteralBodyWithoutPathSugg, SuggAddMissingLetStmt,
SuggEscapeIdentifier, SuggRemoveComma, TernaryOperator, TernaryOperatorSuggestion,
UnexpectedConstInGenericParam, UnexpectedConstParamDeclaration,
UnexpectedConstParamDeclarationSugg, UnmatchedAngleBrackets, UseEqInstead, WrapType,
};
use crate::parser::FnContext;
use crate::parser::attr::InnerAttrPolicy;
@ -2558,36 +2557,6 @@ impl<'a> Parser<'a> {
Ok(false) // Don't continue.
}
/// Attempt to parse a generic const argument that has not been enclosed in braces.
/// There are a limited number of expressions that are permitted without being encoded
/// in braces:
/// - Literals.
/// - Single-segment paths (i.e. standalone generic const parameters).
/// All other expressions that can be parsed will emit an error suggesting the expression be
/// wrapped in braces.
pub(super) fn handle_unambiguous_unbraced_const_arg(&mut self) -> PResult<'a, Box<Expr>> {
let start = self.token.span;
let attrs = self.parse_outer_attributes()?;
let (expr, _) =
self.parse_expr_res(Restrictions::CONST_EXPR, attrs).map_err(|mut err| {
err.span_label(
start.shrink_to_lo(),
"while parsing a const generic argument starting here",
);
err
})?;
if !self.expr_is_valid_const_arg(&expr) {
self.dcx().emit_err(ConstGenericWithoutBraces {
span: expr.span,
sugg: ConstGenericWithoutBracesSugg {
left: expr.span.shrink_to_lo(),
right: expr.span.shrink_to_hi(),
},
});
}
Ok(expr)
}
fn recover_const_param_decl(&mut self, ty_generics: Option<&Generics>) -> Option<GenericArg> {
let snapshot = self.create_snapshot_for_diagnostic();
let param = match self.parse_const_param(AttrVec::new()) {
@ -2623,7 +2592,11 @@ impl<'a> Parser<'a> {
self.dcx().emit_err(UnexpectedConstParamDeclaration { span: param.span(), sugg });
let value = self.mk_expr_err(param.span(), guar);
Some(GenericArg::Const(AnonConst { id: ast::DUMMY_NODE_ID, value }))
Some(GenericArg::Const(AnonConst {
id: ast::DUMMY_NODE_ID,
value,
mgca_disambiguation: MgcaDisambiguation::Direct,
}))
}
pub(super) fn recover_const_param_declaration(
@ -2707,7 +2680,11 @@ impl<'a> Parser<'a> {
);
let guar = err.emit();
let value = self.mk_expr_err(start.to(expr.span), guar);
return Ok(GenericArg::Const(AnonConst { id: ast::DUMMY_NODE_ID, value }));
return Ok(GenericArg::Const(AnonConst {
id: ast::DUMMY_NODE_ID,
value,
mgca_disambiguation: MgcaDisambiguation::Direct,
}));
} else if snapshot.token == token::Colon
&& expr.span.lo() == snapshot.token.span.hi()
&& matches!(expr.kind, ExprKind::Path(..))
@ -2776,7 +2753,11 @@ impl<'a> Parser<'a> {
);
let guar = err.emit();
let value = self.mk_expr_err(span, guar);
GenericArg::Const(AnonConst { id: ast::DUMMY_NODE_ID, value })
GenericArg::Const(AnonConst {
id: ast::DUMMY_NODE_ID,
value,
mgca_disambiguation: MgcaDisambiguation::Direct,
})
}
/// Some special error handling for the "top-level" patterns in a match arm,

View file

@ -15,8 +15,8 @@ use rustc_ast::visit::{Visitor, walk_expr};
use rustc_ast::{
self as ast, AnonConst, Arm, AssignOp, AssignOpKind, AttrStyle, AttrVec, BinOp, BinOpKind,
BlockCheckMode, CaptureBy, ClosureBinder, DUMMY_NODE_ID, Expr, ExprField, ExprKind, FnDecl,
FnRetTy, Label, MacCall, MetaItemLit, Movability, Param, RangeLimits, StmtKind, Ty, TyKind,
UnOp, UnsafeBinderCastKind, YieldKind,
FnRetTy, Label, MacCall, MetaItemLit, MgcaDisambiguation, Movability, Param, RangeLimits,
StmtKind, Ty, TyKind, UnOp, UnsafeBinderCastKind, YieldKind,
};
use rustc_data_structures::stack::ensure_sufficient_stack;
use rustc_errors::{Applicability, Diag, PResult, StashKey, Subdiagnostic};
@ -85,8 +85,15 @@ impl<'a> Parser<'a> {
)
}
pub fn parse_expr_anon_const(&mut self) -> PResult<'a, AnonConst> {
self.parse_expr().map(|value| AnonConst { id: DUMMY_NODE_ID, value })
pub fn parse_expr_anon_const(
&mut self,
mgca_disambiguation: impl FnOnce(&Self, &Expr) -> MgcaDisambiguation,
) -> PResult<'a, AnonConst> {
self.parse_expr().map(|value| AnonConst {
id: DUMMY_NODE_ID,
mgca_disambiguation: mgca_disambiguation(self, &value),
value,
})
}
fn parse_expr_catch_underscore(
@ -1615,7 +1622,18 @@ impl<'a> Parser<'a> {
let first_expr = self.parse_expr()?;
if self.eat(exp!(Semi)) {
// Repeating array syntax: `[ 0; 512 ]`
let count = self.parse_expr_anon_const()?;
let count = if self.token.is_keyword(kw::Const)
&& self.look_ahead(1, |t| *t == token::OpenBrace)
{
// While we could just disambiguate `Direct` from `AnonConst` by
// treating all const block exprs as `AnonConst`, that would
// complicate the DefCollector and likely all other visitors.
// So we strip the const blockiness and just store it as a block
// in the AST with the extra disambiguator on the AnonConst
self.parse_expr_anon_const(|_, _| MgcaDisambiguation::AnonConst)?
} else {
self.parse_expr_anon_const(|this, expr| this.mgca_direct_lit_hack(expr))?
};
self.expect(close)?;
ExprKind::Repeat(first_expr, count)
} else if self.eat(exp!(Comma)) {

View file

@ -1431,7 +1431,7 @@ impl<'a> Parser<'a> {
let rhs = if self.eat(exp!(Eq)) {
if attr::contains_name(attrs, sym::type_const) {
Some(ConstItemRhs::TypeConst(self.parse_expr_anon_const()?))
Some(ConstItemRhs::TypeConst(self.parse_const_arg()?))
} else {
Some(ConstItemRhs::Body(self.parse_expr()?))
}
@ -1650,8 +1650,11 @@ impl<'a> Parser<'a> {
VariantData::Unit(DUMMY_NODE_ID)
};
let disr_expr =
if this.eat(exp!(Eq)) { Some(this.parse_expr_anon_const()?) } else { None };
let disr_expr = if this.eat(exp!(Eq)) {
Some(this.parse_expr_anon_const(|_, _| MgcaDisambiguation::AnonConst)?)
} else {
None
};
let vr = ast::Variant {
ident,
@ -1864,7 +1867,7 @@ impl<'a> Parser<'a> {
if p.token == token::Eq {
let mut snapshot = p.create_snapshot_for_diagnostic();
snapshot.bump();
match snapshot.parse_expr_anon_const() {
match snapshot.parse_expr_anon_const(|_, _| MgcaDisambiguation::AnonConst) {
Ok(const_expr) => {
let sp = ty.span.shrink_to_hi().to(const_expr.value.span);
p.psess.gated_spans.gate(sym::default_field_values, sp);
@ -2066,7 +2069,7 @@ impl<'a> Parser<'a> {
}
let default = if self.token == token::Eq {
self.bump();
let const_expr = self.parse_expr_anon_const()?;
let const_expr = self.parse_expr_anon_const(|_, _| MgcaDisambiguation::AnonConst)?;
let sp = ty.span.shrink_to_hi().to(const_expr.value.span);
self.psess.gated_spans.gate(sym::default_field_values, sp);
Some(const_expr)

View file

@ -35,9 +35,9 @@ use rustc_ast::tokenstream::{
};
use rustc_ast::util::case::Case;
use rustc_ast::{
self as ast, AnonConst, AttrArgs, AttrId, ByRef, Const, CoroutineKind, DUMMY_NODE_ID,
DelimArgs, Expr, ExprKind, Extern, HasAttrs, HasTokens, Mutability, Recovered, Safety, StrLit,
Visibility, VisibilityKind,
self as ast, AnonConst, AttrArgs, AttrId, BlockCheckMode, ByRef, Const, CoroutineKind,
DUMMY_NODE_ID, DelimArgs, Expr, ExprKind, Extern, HasAttrs, HasTokens, MgcaDisambiguation,
Mutability, Recovered, Safety, StrLit, Visibility, VisibilityKind,
};
use rustc_ast_pretty::pprust;
use rustc_data_structures::fx::FxHashMap;
@ -727,7 +727,10 @@ impl<'a> Parser<'a> {
}
fn check_const_arg(&mut self) -> bool {
self.check_or_expected(self.token.can_begin_const_arg(), TokenType::Const)
let is_mcg_arg = self.check_or_expected(self.token.can_begin_const_arg(), TokenType::Const);
let is_mgca_arg = self.is_keyword_ahead(0, &[kw::Const])
&& self.look_ahead(1, |t| *t == token::OpenBrace);
is_mcg_arg || is_mgca_arg
}
fn check_const_closure(&self) -> bool {
@ -1299,6 +1302,20 @@ impl<'a> Parser<'a> {
}
}
fn parse_mgca_const_block(&mut self, gate_syntax: bool) -> PResult<'a, AnonConst> {
self.expect_keyword(exp!(Const))?;
let kw_span = self.token.span;
let value = self.parse_expr_block(None, self.token.span, BlockCheckMode::Default)?;
if gate_syntax {
self.psess.gated_spans.gate(sym::min_generic_const_args, kw_span.to(value.span));
}
Ok(AnonConst {
id: ast::DUMMY_NODE_ID,
value,
mgca_disambiguation: MgcaDisambiguation::AnonConst,
})
}
/// Parses inline const expressions.
fn parse_const_block(&mut self, span: Span, pat: bool) -> PResult<'a, Box<Expr>> {
self.expect_keyword(exp!(Const))?;
@ -1306,6 +1323,7 @@ impl<'a> Parser<'a> {
let anon_const = AnonConst {
id: DUMMY_NODE_ID,
value: self.mk_expr(blk.span, ExprKind::Block(blk, None)),
mgca_disambiguation: MgcaDisambiguation::AnonConst,
};
let blk_span = anon_const.value.span;
let kind = if pat {

View file

@ -4,8 +4,8 @@ use ast::token::IdentIsRaw;
use rustc_ast::token::{self, MetaVarKind, Token, TokenKind};
use rustc_ast::{
self as ast, AngleBracketedArg, AngleBracketedArgs, AnonConst, AssocItemConstraint,
AssocItemConstraintKind, BlockCheckMode, GenericArg, GenericArgs, Generics, ParenthesizedArgs,
Path, PathSegment, QSelf,
AssocItemConstraintKind, BlockCheckMode, GenericArg, GenericArgs, Generics, MgcaDisambiguation,
ParenthesizedArgs, Path, PathSegment, QSelf,
};
use rustc_errors::{Applicability, Diag, PResult};
use rustc_span::{BytePos, Ident, Span, kw, sym};
@ -16,12 +16,13 @@ use super::ty::{AllowPlus, RecoverQPath, RecoverReturnSign};
use super::{Parser, Restrictions, TokenType};
use crate::ast::{PatKind, TyKind};
use crate::errors::{
self, AttributeOnEmptyType, AttributeOnGenericArg, FnPathFoundNamedParams,
PathFoundAttributeInParams, PathFoundCVariadicParams, PathSingleColon, PathTripleColon,
self, AttributeOnEmptyType, AttributeOnGenericArg, ConstGenericWithoutBraces,
ConstGenericWithoutBracesSugg, FnPathFoundNamedParams, PathFoundAttributeInParams,
PathFoundCVariadicParams, PathSingleColon, PathTripleColon,
};
use crate::exp;
use crate::parser::{
CommaRecoveryMode, ExprKind, FnContext, FnParseMode, RecoverColon, RecoverComma,
CommaRecoveryMode, Expr, ExprKind, FnContext, FnParseMode, RecoverColon, RecoverComma,
};
/// Specifies how to parse a path.
@ -870,12 +871,75 @@ impl<'a> Parser<'a> {
/// the caller.
pub(super) fn parse_const_arg(&mut self) -> PResult<'a, AnonConst> {
// Parse const argument.
let value = if self.token.kind == token::OpenBrace {
self.parse_expr_block(None, self.token.span, BlockCheckMode::Default)?
let (value, mgca_disambiguation) = if self.token.kind == token::OpenBrace {
let value = self.parse_expr_block(None, self.token.span, BlockCheckMode::Default)?;
(value, MgcaDisambiguation::Direct)
} else if self.token.is_keyword(kw::Const) {
// While we could just disambiguate `Direct` from `AnonConst` by
// treating all const block exprs as `AnonConst`, that would
// complicate the DefCollector and likely all other visitors.
// So we strip the const blockiness and just store it as a block
// in the AST with the extra disambiguator on the AnonConst
let value = self.parse_mgca_const_block(true)?;
(value.value, MgcaDisambiguation::AnonConst)
} else {
self.handle_unambiguous_unbraced_const_arg()?
self.parse_unambiguous_unbraced_const_arg()?
};
Ok(AnonConst { id: ast::DUMMY_NODE_ID, value })
Ok(AnonConst { id: ast::DUMMY_NODE_ID, value, mgca_disambiguation })
}
/// Attempt to parse a const argument that has not been enclosed in braces.
/// There are a limited number of expressions that are permitted without being
/// enclosed in braces:
/// - Literals.
/// - Single-segment paths (i.e. standalone generic const parameters).
/// All other expressions that can be parsed will emit an error suggesting the expression be
/// wrapped in braces.
pub(super) fn parse_unambiguous_unbraced_const_arg(
&mut self,
) -> PResult<'a, (Box<Expr>, MgcaDisambiguation)> {
let start = self.token.span;
let attrs = self.parse_outer_attributes()?;
let (expr, _) =
self.parse_expr_res(Restrictions::CONST_EXPR, attrs).map_err(|mut err| {
err.span_label(
start.shrink_to_lo(),
"while parsing a const generic argument starting here",
);
err
})?;
if !self.expr_is_valid_const_arg(&expr) {
self.dcx().emit_err(ConstGenericWithoutBraces {
span: expr.span,
sugg: ConstGenericWithoutBracesSugg {
left: expr.span.shrink_to_lo(),
right: expr.span.shrink_to_hi(),
},
});
}
let mgca_disambiguation = self.mgca_direct_lit_hack(&expr);
Ok((expr, mgca_disambiguation))
}
/// Under `min_generic_const_args` we still allow *some* anon consts to be written without
/// a `const` block as it makes things quite a lot nicer. This function is useful for contexts
/// where we would like to use `MgcaDisambiguation::Direct` but need to fudge it to be `AnonConst`
/// in the presence of literals.
//
/// FIXME(min_generic_const_args): In the long term it would be nice to have a way to directly
/// represent literals in `hir::ConstArgKind` so that we can remove this special case by not
/// needing an anon const.
pub fn mgca_direct_lit_hack(&self, expr: &Expr) -> MgcaDisambiguation {
match &expr.kind {
ast::ExprKind::Lit(_) => MgcaDisambiguation::AnonConst,
ast::ExprKind::Unary(ast::UnOp::Neg, expr)
if matches!(expr.kind, ast::ExprKind::Lit(_)) =>
{
MgcaDisambiguation::AnonConst
}
_ => MgcaDisambiguation::Direct,
}
}
/// Parse a generic argument in a path segment.
@ -976,7 +1040,11 @@ impl<'a> Parser<'a> {
GenericArg::Type(_) => GenericArg::Type(self.mk_ty(attr_span, TyKind::Err(guar))),
GenericArg::Const(_) => {
let error_expr = self.mk_expr(attr_span, ExprKind::Err(guar));
GenericArg::Const(AnonConst { id: ast::DUMMY_NODE_ID, value: error_expr })
GenericArg::Const(AnonConst {
id: ast::DUMMY_NODE_ID,
value: error_expr,
mgca_disambiguation: MgcaDisambiguation::Direct,
})
}
GenericArg::Lifetime(lt) => GenericArg::Lifetime(lt),
}));

View file

@ -2,9 +2,9 @@ use rustc_ast::token::{self, IdentIsRaw, MetaVarKind, Token, TokenKind};
use rustc_ast::util::case::Case;
use rustc_ast::{
self as ast, BoundAsyncness, BoundConstness, BoundPolarity, DUMMY_NODE_ID, FnPtrTy, FnRetTy,
GenericBound, GenericBounds, GenericParam, Generics, Lifetime, MacCall, MutTy, Mutability,
Pinnedness, PolyTraitRef, PreciseCapturingArg, TraitBoundModifiers, TraitObjectSyntax, Ty,
TyKind, UnsafeBinderTy,
GenericBound, GenericBounds, GenericParam, Generics, Lifetime, MacCall, MgcaDisambiguation,
MutTy, Mutability, Pinnedness, PolyTraitRef, PreciseCapturingArg, TraitBoundModifiers,
TraitObjectSyntax, Ty, TyKind, UnsafeBinderTy,
};
use rustc_data_structures::stack::ensure_sufficient_stack;
use rustc_errors::{Applicability, Diag, E0516, PResult};
@ -658,7 +658,19 @@ impl<'a> Parser<'a> {
};
let ty = if self.eat(exp!(Semi)) {
let mut length = self.parse_expr_anon_const()?;
let mut length = if self.token.is_keyword(kw::Const)
&& self.look_ahead(1, |t| *t == token::OpenBrace)
{
// While we could just disambiguate `Direct` from `AnonConst` by
// treating all const block exprs as `AnonConst`, that would
// complicate the DefCollector and likely all other visitors.
// So we strip the const blockiness and just store it as a block
// in the AST with the extra disambiguator on the AnonConst
self.parse_mgca_const_block(false)?
} else {
self.parse_expr_anon_const(|this, expr| this.mgca_direct_lit_hack(expr))?
};
if let Err(e) = self.expect(exp!(CloseBracket)) {
// Try to recover from `X<Y, ...>` when `X::<Y, ...>` works
self.check_mistyped_turbofish_with_multiple_type_params(e, &mut length.value)?;
@ -699,8 +711,9 @@ impl<'a> Parser<'a> {
_ = self.eat(exp!(Comma)) || self.eat(exp!(Colon)) || self.eat(exp!(Star));
let suggestion_span = self.prev_token.span.with_lo(hi);
// FIXME(mgca): recovery is broken for `const {` args
// we first try to parse pattern like `[u8 5]`
let length = match self.parse_expr_anon_const() {
let length = match self.parse_expr_anon_const(|_, _| MgcaDisambiguation::Direct) {
Ok(length) => length,
Err(e) => {
e.cancel();
@ -788,7 +801,7 @@ impl<'a> Parser<'a> {
/// an error type.
fn parse_typeof_ty(&mut self, lo: Span) -> PResult<'a, TyKind> {
self.expect(exp!(OpenParen))?;
let _expr = self.parse_expr_anon_const()?;
let _expr = self.parse_expr_anon_const(|_, _| MgcaDisambiguation::AnonConst)?;
self.expect(exp!(CloseParen))?;
let span = lo.to(self.prev_token.span);
let guar = self

View file

@ -8,7 +8,6 @@ edition = "2024"
rustc_abi = { path = "../rustc_abi" }
rustc_ast = { path = "../rustc_ast" }
rustc_ast_lowering = { path = "../rustc_ast_lowering" }
rustc_ast_pretty = { path = "../rustc_ast_pretty" }
rustc_attr_parsing = { path = "../rustc_attr_parsing" }
rustc_data_structures = { path = "../rustc_data_structures" }
rustc_errors = { path = "../rustc_errors" }

View file

@ -106,60 +106,15 @@ passes_diagnostic_diagnostic_on_unimplemented_only_for_traits =
passes_diagnostic_item_first_defined =
the diagnostic item is first defined here
passes_doc_alias_bad_char =
{$char_} character isn't allowed in {$attr_str}
passes_doc_alias_bad_location =
{$attr_str} isn't allowed on {$location}
passes_doc_alias_duplicated = doc alias is duplicated
.label = first defined here
passes_doc_alias_empty =
{$attr_str} attribute cannot have empty value
passes_doc_alias_malformed =
doc alias attribute expects a string `#[doc(alias = "a")]` or a list of strings `#[doc(alias("a", "b"))]`
`#[doc(alias = "...")]` isn't allowed on {$location}
passes_doc_alias_not_an_alias =
{$attr_str} is the same as the item's name
passes_doc_alias_not_string_literal =
`#[doc(alias("a"))]` expects string literals
passes_doc_alias_start_end =
{$attr_str} cannot start or end with ' '
passes_doc_attr_expects_no_value =
`doc({$attr_name})` does not accept a value
.suggestion = use `doc({$attr_name})`
passes_doc_attr_expects_string =
`doc({$attr_name})` expects a string value
.suggestion = use `doc({$attr_name} = "...")`
`#[doc(alias = "{$attr_str}"]` is the same as the item's name
passes_doc_attr_not_crate_level =
`#![doc({$attr_name} = "...")]` isn't allowed as a crate-level attribute
passes_doc_attribute_not_attribute =
nonexistent builtin attribute `{$attribute}` used in `#[doc(attribute = "...")]`
.help = only existing builtin attributes are allowed in core/std
passes_doc_auto_cfg_expects_hide_or_show =
only `hide` or `show` are allowed in `#[doc(auto_cfg(...))]`
passes_doc_auto_cfg_hide_show_expects_list =
`#![doc(auto_cfg({$attr_name}(...)))]` expects a list of items
passes_doc_auto_cfg_hide_show_unexpected_item =
`#![doc(auto_cfg({$attr_name}(...)))]` only accepts identifiers or key/value items
passes_doc_auto_cfg_wrong_literal =
expected boolean for `#[doc(auto_cfg = ...)]`
passes_doc_expect_str =
doc {$attr_name} attribute expects a string: #[doc({$attr_name} = "a")]
passes_doc_fake_variadic_not_valid =
`#[doc(fake_variadic)]` must be used on the first of a set of tuple or fn pointer trait impls with varying arity
@ -179,19 +134,12 @@ passes_doc_inline_only_use =
.not_a_use_item_label = not a `use` item
.note = read <https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#inline-and-no_inline> for more information
passes_doc_invalid =
invalid `doc` attribute
passes_doc_keyword_attribute_empty_mod =
`#[doc({$attr_name} = "...")]` should be used on empty modules
passes_doc_keyword_attribute_not_mod =
`#[doc({$attr_name} = "...")]` should be used on modules
passes_doc_keyword_not_keyword =
nonexistent keyword `{$keyword}` used in `#[doc(keyword = "...")]`
.help = only existing keywords are allowed in core/std
passes_doc_keyword_only_impl =
`#[doc(keyword = "...")]` should be used on impl blocks
@ -212,39 +160,6 @@ passes_doc_rust_logo =
passes_doc_search_unbox_invalid =
`#[doc(search_unbox)]` should be used on generic structs and enums
passes_doc_test_literal = `#![doc(test(...)]` does not take a literal
passes_doc_test_takes_list =
`#[doc(test(...)]` takes a list of attributes
passes_doc_test_unknown =
unknown `doc(test)` attribute `{$path}`
passes_doc_test_unknown_any =
unknown `doc` attribute `{$path}`
passes_doc_test_unknown_include =
unknown `doc` attribute `{$path}`
.suggestion = use `doc = include_str!` instead
passes_doc_test_unknown_passes =
unknown `doc` attribute `{$path}`
.note = `doc` attribute `{$path}` no longer functions; see issue #44136 <https://github.com/rust-lang/rust/issues/44136>
.label = no longer functions
.no_op_note = `doc({$path})` is now a no-op
passes_doc_test_unknown_plugins =
unknown `doc` attribute `{$path}`
.note = `doc` attribute `{$path}` no longer functions; see issue #44136 <https://github.com/rust-lang/rust/issues/44136> and CVE-2018-1000622 <https://nvd.nist.gov/vuln/detail/CVE-2018-1000622>
.label = no longer functions
.no_op_note = `doc({$path})` is now a no-op
passes_doc_test_unknown_spotlight =
unknown `doc` attribute `{$path}`
.note = `doc(spotlight)` was renamed to `doc(notable_trait)`
.suggestion = use `notable_trait` instead
.no_op_note = `doc(spotlight)` is now a no-op
passes_duplicate_diagnostic_item_in_crate =
duplicate diagnostic item in crate `{$crate_name}`: `{$name}`
.note = the diagnostic item is first defined in crate `{$orig_crate_name}`

View file

@ -10,22 +10,25 @@ use std::collections::hash_map::Entry;
use std::slice;
use rustc_abi::{Align, ExternAbi, Size};
use rustc_ast::{AttrStyle, LitKind, MetaItem, MetaItemInner, MetaItemKind, ast};
use rustc_ast::{AttrStyle, LitKind, MetaItemKind, ast};
use rustc_attr_parsing::{AttributeParser, Late};
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::{Applicability, DiagCtxtHandle, IntoDiagArg, MultiSpan, StashKey};
use rustc_errors::{DiagCtxtHandle, IntoDiagArg, MultiSpan, StashKey};
use rustc_feature::{
ACCEPTED_LANG_FEATURES, AttributeDuplicates, AttributeType, BUILTIN_ATTRIBUTE_MAP,
BuiltinAttribute,
};
use rustc_hir::attrs::{AttributeKind, InlineAttr, MirDialect, MirPhase, ReprAttr, SanitizerSet};
use rustc_hir::attrs::{
AttributeKind, DocAttribute, DocInline, InlineAttr, MirDialect, MirPhase, ReprAttr,
SanitizerSet,
};
use rustc_hir::def::DefKind;
use rustc_hir::def_id::LocalModDefId;
use rustc_hir::intravisit::{self, Visitor};
use rustc_hir::{
self as hir, Attribute, CRATE_HIR_ID, CRATE_OWNER_ID, Constness, FnSig, ForeignItem, HirId,
Item, ItemKind, MethodKind, PartialConstStability, Safety, Stability, StabilityLevel, Target,
TraitItem, find_attr,
self as hir, Attribute, CRATE_HIR_ID, Constness, FnSig, ForeignItem, HirId, Item, ItemKind,
MethodKind, PartialConstStability, Safety, Stability, StabilityLevel, Target, TraitItem,
find_attr,
};
use rustc_macros::LintDiagnostic;
use rustc_middle::hir::nested_filter;
@ -43,7 +46,7 @@ use rustc_session::lint::builtin::{
};
use rustc_session::parse::feature_err;
use rustc_span::edition::Edition;
use rustc_span::{BytePos, DUMMY_SP, Span, Symbol, edition, sym};
use rustc_span::{BytePos, DUMMY_SP, Span, Symbol, sym};
use rustc_trait_selection::error_reporting::InferCtxtErrorExt;
use rustc_trait_selection::infer::{TyCtxtInferExt, ValuePairs};
use rustc_trait_selection::traits::ObligationCtxt;
@ -106,21 +109,6 @@ impl IntoDiagArg for ProcMacroKind {
}
}
#[derive(Clone, Copy)]
enum DocFakeItemKind {
Attribute,
Keyword,
}
impl DocFakeItemKind {
fn name(self) -> &'static str {
match self {
Self::Attribute => "attribute",
Self::Keyword => "keyword",
}
}
}
struct CheckAttrVisitor<'tcx> {
tcx: TyCtxt<'tcx>,
@ -141,8 +129,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
target: Target,
item: Option<ItemLike<'_>>,
) {
let mut doc_aliases = FxHashMap::default();
let mut specified_inline = None;
let mut seen = FxHashMap::default();
let attrs = self.tcx.hir_attrs(hir_id);
for attr in attrs {
@ -225,6 +211,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
Attribute::Parsed(AttributeKind::MacroExport { span, .. }) => {
self.check_macro_export(hir_id, *span, target)
},
Attribute::Parsed(AttributeKind::Doc(attr)) => self.check_doc_attrs(attr, hir_id, target),
Attribute::Parsed(
AttributeKind::BodyStability { .. }
| AttributeKind::ConstStabilityIndirect
@ -306,15 +293,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
self.check_diagnostic_on_const(attr.span(), hir_id, target, item)
}
[sym::thread_local, ..] => self.check_thread_local(attr, span, target),
[sym::doc, ..] => self.check_doc_attrs(
attr,
attr.span(),
attr_item.style,
hir_id,
target,
&mut specified_inline,
&mut doc_aliases,
),
[sym::no_link, ..] => self.check_no_link(hir_id, attr, span, target),
[sym::rustc_no_implicit_autorefs, ..] => {
self.check_applied_to_fn_or_method(hir_id, attr.span(), span, target)
@ -782,42 +760,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
}
}
fn doc_attr_str_error(&self, meta: &MetaItemInner, attr_name: &str) {
self.dcx().emit_err(errors::DocExpectStr { attr_span: meta.span(), attr_name });
}
fn check_doc_alias_value(
&self,
meta: &MetaItemInner,
doc_alias: Symbol,
hir_id: HirId,
target: Target,
is_list: bool,
aliases: &mut FxHashMap<String, Span>,
) {
let tcx = self.tcx;
let span = meta.name_value_literal_span().unwrap_or_else(|| meta.span());
let attr_str =
&format!("`#[doc(alias{})]`", if is_list { "(\"...\")" } else { " = \"...\"" });
if doc_alias == sym::empty {
tcx.dcx().emit_err(errors::DocAliasEmpty { span, attr_str });
return;
}
let doc_alias_str = doc_alias.as_str();
if let Some(c) = doc_alias_str
.chars()
.find(|&c| c == '"' || c == '\'' || (c.is_whitespace() && c != ' '))
{
tcx.dcx().emit_err(errors::DocAliasBadChar { span, attr_str, char_: c });
return;
}
if doc_alias_str.starts_with(' ') || doc_alias_str.ends_with(' ') {
tcx.dcx().emit_err(errors::DocAliasStartEnd { span, attr_str });
return;
}
let span = meta.span();
fn check_doc_alias_value(&self, span: Span, hir_id: HirId, target: Target, alias: Symbol) {
if let Some(location) = match target {
Target::AssocTy => {
if let DefKind::Impl { .. } =
@ -874,123 +817,16 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
| Target::MacroCall
| Target::Delegation { .. } => None,
} {
tcx.dcx().emit_err(errors::DocAliasBadLocation { span, attr_str, location });
self.tcx.dcx().emit_err(errors::DocAliasBadLocation { span, location });
return;
}
if self.tcx.hir_opt_name(hir_id) == Some(doc_alias) {
tcx.dcx().emit_err(errors::DocAliasNotAnAlias { span, attr_str });
if self.tcx.hir_opt_name(hir_id) == Some(alias) {
self.tcx.dcx().emit_err(errors::DocAliasNotAnAlias { span, attr_str: alias });
return;
}
if let Err(entry) = aliases.try_insert(doc_alias_str.to_owned(), span) {
self.tcx.emit_node_span_lint(
UNUSED_ATTRIBUTES,
hir_id,
span,
errors::DocAliasDuplicated { first_defn: *entry.entry.get() },
);
}
}
fn check_doc_alias(
&self,
meta: &MetaItemInner,
hir_id: HirId,
target: Target,
aliases: &mut FxHashMap<String, Span>,
) {
if let Some(values) = meta.meta_item_list() {
for v in values {
match v.lit() {
Some(l) => match l.kind {
LitKind::Str(s, _) => {
self.check_doc_alias_value(v, s, hir_id, target, true, aliases);
}
_ => {
self.tcx
.dcx()
.emit_err(errors::DocAliasNotStringLiteral { span: v.span() });
}
},
None => {
self.tcx
.dcx()
.emit_err(errors::DocAliasNotStringLiteral { span: v.span() });
}
}
}
} else if let Some(doc_alias) = meta.value_str() {
self.check_doc_alias_value(meta, doc_alias, hir_id, target, false, aliases)
} else {
self.dcx().emit_err(errors::DocAliasMalformed { span: meta.span() });
}
}
fn check_doc_keyword_and_attribute(
&self,
meta: &MetaItemInner,
hir_id: HirId,
attr_kind: DocFakeItemKind,
) {
fn is_doc_keyword(s: Symbol) -> bool {
// FIXME: Once rustdoc can handle URL conflicts on case insensitive file systems, we
// can remove the `SelfTy` case here, remove `sym::SelfTy`, and update the
// `#[doc(keyword = "SelfTy")` attribute in `library/std/src/keyword_docs.rs`.
s.is_reserved(|| edition::LATEST_STABLE_EDITION) || s.is_weak() || s == sym::SelfTy
}
// FIXME: This should support attributes with namespace like `diagnostic::do_not_recommend`.
fn is_builtin_attr(s: Symbol) -> bool {
rustc_feature::BUILTIN_ATTRIBUTE_MAP.contains_key(&s)
}
let value = match meta.value_str() {
Some(value) if value != sym::empty => value,
_ => return self.doc_attr_str_error(meta, attr_kind.name()),
};
let item_kind = match self.tcx.hir_node(hir_id) {
hir::Node::Item(item) => Some(&item.kind),
_ => None,
};
match item_kind {
Some(ItemKind::Mod(_, module)) => {
if !module.item_ids.is_empty() {
self.dcx().emit_err(errors::DocKeywordAttributeEmptyMod {
span: meta.span(),
attr_name: attr_kind.name(),
});
return;
}
}
_ => {
self.dcx().emit_err(errors::DocKeywordAttributeNotMod {
span: meta.span(),
attr_name: attr_kind.name(),
});
return;
}
}
match attr_kind {
DocFakeItemKind::Keyword => {
if !is_doc_keyword(value) {
self.dcx().emit_err(errors::DocKeywordNotKeyword {
span: meta.name_value_literal_span().unwrap_or_else(|| meta.span()),
keyword: value,
});
}
}
DocFakeItemKind::Attribute => {
if !is_builtin_attr(value) {
self.dcx().emit_err(errors::DocAttributeNotAttribute {
span: meta.name_value_literal_span().unwrap_or_else(|| meta.span()),
attribute: value,
});
}
}
}
}
fn check_doc_fake_variadic(&self, meta: &MetaItemInner, hir_id: HirId) {
fn check_doc_fake_variadic(&self, span: Span, hir_id: HirId) {
let item_kind = match self.tcx.hir_node(hir_id) {
hir::Node::Item(item) => Some(&item.kind),
_ => None,
@ -1008,18 +844,18 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
false
};
if !is_valid {
self.dcx().emit_err(errors::DocFakeVariadicNotValid { span: meta.span() });
self.dcx().emit_err(errors::DocFakeVariadicNotValid { span });
}
}
_ => {
self.dcx().emit_err(errors::DocKeywordOnlyImpl { span: meta.span() });
self.dcx().emit_err(errors::DocKeywordOnlyImpl { span });
}
}
}
fn check_doc_search_unbox(&self, meta: &MetaItemInner, hir_id: HirId) {
fn check_doc_search_unbox(&self, span: Span, hir_id: HirId) {
let hir::Node::Item(item) = self.tcx.hir_node(hir_id) else {
self.dcx().emit_err(errors::DocSearchUnboxInvalid { span: meta.span() });
self.dcx().emit_err(errors::DocSearchUnboxInvalid { span });
return;
};
match item.kind {
@ -1032,7 +868,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
}) => {}
ItemKind::TyAlias(_, generics, _) if generics.params.len() != 0 => {}
_ => {
self.dcx().emit_err(errors::DocSearchUnboxInvalid { span: meta.span() });
self.dcx().emit_err(errors::DocSearchUnboxInvalid { span });
}
}
}
@ -1046,60 +882,49 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
/// already seen an inlining attribute for this item.
/// If so, `specified_inline` holds the value and the span of
/// the first `inline`/`no_inline` attribute.
fn check_doc_inline(
&self,
style: AttrStyle,
meta: &MetaItemInner,
hir_id: HirId,
target: Target,
specified_inline: &mut Option<(bool, Span)>,
) {
match target {
Target::Use | Target::ExternCrate => {
let do_inline = meta.has_name(sym::inline);
if let Some((prev_inline, prev_span)) = *specified_inline {
if do_inline != prev_inline {
let mut spans = MultiSpan::from_spans(vec![prev_span, meta.span()]);
spans.push_span_label(prev_span, fluent::passes_doc_inline_conflict_first);
spans.push_span_label(
meta.span(),
fluent::passes_doc_inline_conflict_second,
);
self.dcx().emit_err(errors::DocKeywordConflict { spans });
fn check_doc_inline(&self, hir_id: HirId, target: Target, inline: &[(DocInline, Span)]) {
let span = match inline {
[] => return,
[(_, span)] => *span,
[(inline, span), rest @ ..] => {
for (inline2, span2) in rest {
if inline2 != inline {
let mut spans = MultiSpan::from_spans(vec![*span, *span2]);
spans.push_span_label(*span, fluent::passes_doc_inline_conflict_first);
spans.push_span_label(*span2, fluent::passes_doc_inline_conflict_second);
self.dcx().emit_err(errors::DocInlineConflict { spans });
return;
}
} else {
*specified_inline = Some((do_inline, meta.span()));
}
*span
}
};
match target {
Target::Use | Target::ExternCrate => {}
_ => {
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
meta.span(),
span,
errors::DocInlineOnlyUse {
attr_span: meta.span(),
item_span: (style == AttrStyle::Outer).then(|| self.tcx.hir_span(hir_id)),
attr_span: span,
item_span: self.tcx.hir_span(hir_id),
},
);
}
}
}
fn check_doc_masked(
&self,
style: AttrStyle,
meta: &MetaItemInner,
hir_id: HirId,
target: Target,
) {
fn check_doc_masked(&self, span: Span, hir_id: HirId, target: Target) {
if target != Target::ExternCrate {
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
meta.span(),
span,
errors::DocMaskedOnlyExternCrate {
attr_span: meta.span(),
item_span: (style == AttrStyle::Outer).then(|| self.tcx.hir_span(hir_id)),
attr_span: span,
item_span: self.tcx.hir_span(hir_id),
},
);
return;
@ -1109,354 +934,160 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
meta.span(),
span,
errors::DocMaskedNotExternCrateSelf {
attr_span: meta.span(),
item_span: (style == AttrStyle::Outer).then(|| self.tcx.hir_span(hir_id)),
attr_span: span,
item_span: self.tcx.hir_span(hir_id),
},
);
}
}
fn check_doc_keyword_and_attribute(&self, span: Span, hir_id: HirId, attr_name: &'static str) {
let item_kind = match self.tcx.hir_node(hir_id) {
hir::Node::Item(item) => Some(&item.kind),
_ => None,
};
match item_kind {
Some(ItemKind::Mod(_, module)) => {
if !module.item_ids.is_empty() {
self.dcx().emit_err(errors::DocKeywordAttributeEmptyMod { span, attr_name });
return;
}
}
_ => {
self.dcx().emit_err(errors::DocKeywordAttributeNotMod { span, attr_name });
return;
}
}
}
/// Checks that an attribute is *not* used at the crate level. Returns `true` if valid.
fn check_attr_not_crate_level(
&self,
meta: &MetaItemInner,
hir_id: HirId,
attr_name: &str,
) -> bool {
fn check_attr_not_crate_level(&self, span: Span, hir_id: HirId, attr_name: &str) -> bool {
if CRATE_HIR_ID == hir_id {
self.dcx().emit_err(errors::DocAttrNotCrateLevel { span: meta.span(), attr_name });
self.dcx().emit_err(errors::DocAttrNotCrateLevel { span, attr_name });
return false;
}
true
}
/// Checks that an attribute is used at the crate level. Returns `true` if valid.
fn check_attr_crate_level(
&self,
attr_span: Span,
style: AttrStyle,
meta: &MetaItemInner,
hir_id: HirId,
) -> bool {
fn check_attr_crate_level(&self, span: Span, hir_id: HirId) -> bool {
if hir_id != CRATE_HIR_ID {
// insert a bang between `#` and `[...`
let bang_span = attr_span.lo() + BytePos(1);
let sugg = (style == AttrStyle::Outer
&& self.tcx.hir_get_parent_item(hir_id) == CRATE_OWNER_ID)
.then_some(errors::AttrCrateLevelOnlySugg {
attr: attr_span.with_lo(bang_span).with_hi(bang_span),
});
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
meta.span(),
errors::AttrCrateLevelOnly { sugg },
span,
errors::AttrCrateLevelOnly {},
);
return false;
}
true
}
fn check_doc_attr_string_value(&self, meta: &MetaItemInner, hir_id: HirId) {
if meta.value_str().is_none() {
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
meta.span(),
errors::DocAttrExpectsString { attr_name: meta.name().unwrap() },
);
}
}
fn check_doc_attr_no_value(&self, meta: &MetaItemInner, hir_id: HirId) {
if !meta.is_word() {
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
meta.span(),
errors::DocAttrExpectsNoValue { attr_name: meta.name().unwrap() },
);
}
}
/// Checks that `doc(test(...))` attribute contains only valid attributes and are at the right place.
fn check_test_attr(
&self,
attr_span: Span,
style: AttrStyle,
meta: &MetaItemInner,
hir_id: HirId,
) {
if let Some(metas) = meta.meta_item_list() {
for i_meta in metas {
match (i_meta.name(), i_meta.meta_item()) {
(Some(sym::attr), _) => {
// Allowed everywhere like `#[doc]`
}
(Some(sym::no_crate_inject), _) => {
self.check_attr_crate_level(attr_span, style, meta, hir_id);
}
(_, Some(m)) => {
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
i_meta.span(),
errors::DocTestUnknown {
path: rustc_ast_pretty::pprust::path_to_string(&m.path),
},
);
}
(_, None) => {
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
i_meta.span(),
errors::DocTestLiteral,
);
}
}
}
} else {
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
meta.span(),
errors::DocTestTakesList,
);
}
}
/// Check that the `#![doc(auto_cfg)]` attribute has the expected input.
fn check_doc_auto_cfg(&self, meta: &MetaItem, hir_id: HirId) {
match &meta.kind {
MetaItemKind::Word => {}
MetaItemKind::NameValue(lit) => {
if !matches!(lit.kind, LitKind::Bool(_)) {
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
meta.span,
errors::DocAutoCfgWrongLiteral,
);
}
}
MetaItemKind::List(list) => {
for item in list {
let Some(attr_name @ (sym::hide | sym::show)) = item.name() else {
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
meta.span,
errors::DocAutoCfgExpectsHideOrShow,
);
continue;
};
if let Some(list) = item.meta_item_list() {
for item in list {
let valid = item.meta_item().is_some_and(|meta| {
meta.path.segments.len() == 1
&& matches!(
&meta.kind,
MetaItemKind::Word | MetaItemKind::NameValue(_)
)
});
if !valid {
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
item.span(),
errors::DocAutoCfgHideShowUnexpectedItem { attr_name },
);
}
}
} else {
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
meta.span,
errors::DocAutoCfgHideShowExpectsList { attr_name },
);
}
}
}
}
}
/// Runs various checks on `#[doc]` attributes.
///
/// `specified_inline` should be initialized to `None` and kept for the scope
/// of one item. Read the documentation of [`check_doc_inline`] for more information.
///
/// [`check_doc_inline`]: Self::check_doc_inline
fn check_doc_attrs(
&self,
attr: &Attribute,
attr_span: Span,
style: AttrStyle,
hir_id: HirId,
target: Target,
specified_inline: &mut Option<(bool, Span)>,
aliases: &mut FxHashMap<String, Span>,
) {
if let Some(list) = attr.meta_item_list() {
for meta in &list {
if let Some(i_meta) = meta.meta_item() {
match i_meta.name() {
Some(sym::alias) => {
if self.check_attr_not_crate_level(meta, hir_id, "alias") {
self.check_doc_alias(meta, hir_id, target, aliases);
}
}
fn check_doc_attrs(&self, attr: &DocAttribute, hir_id: HirId, target: Target) {
let DocAttribute {
aliases,
// valid pretty much anywhere, not checked here?
// FIXME: should we?
hidden: _,
inline,
// FIXME: currently unchecked
cfg: _,
// already check in attr_parsing
auto_cfg: _,
// already check in attr_parsing
auto_cfg_change: _,
fake_variadic,
keyword,
masked,
// FIXME: currently unchecked
notable_trait: _,
search_unbox,
html_favicon_url,
html_logo_url,
html_playground_url,
html_root_url,
html_no_source,
issue_tracker_base_url,
rust_logo,
// allowed anywhere
test_attrs: _,
no_crate_inject,
attribute,
} = attr;
Some(sym::keyword) => {
if self.check_attr_not_crate_level(meta, hir_id, "keyword") {
self.check_doc_keyword_and_attribute(
meta,
hir_id,
DocFakeItemKind::Keyword,
);
}
}
Some(sym::attribute) => {
if self.check_attr_not_crate_level(meta, hir_id, "attribute") {
self.check_doc_keyword_and_attribute(
meta,
hir_id,
DocFakeItemKind::Attribute,
);
}
}
Some(sym::fake_variadic) => {
if self.check_attr_not_crate_level(meta, hir_id, "fake_variadic") {
self.check_doc_fake_variadic(meta, hir_id);
}
}
Some(sym::search_unbox) => {
if self.check_attr_not_crate_level(meta, hir_id, "fake_variadic") {
self.check_doc_search_unbox(meta, hir_id);
}
}
Some(sym::test) => {
self.check_test_attr(attr_span, style, meta, hir_id);
}
Some(
sym::html_favicon_url
| sym::html_logo_url
| sym::html_playground_url
| sym::issue_tracker_base_url
| sym::html_root_url,
) => {
self.check_attr_crate_level(attr_span, style, meta, hir_id);
self.check_doc_attr_string_value(meta, hir_id);
}
Some(sym::html_no_source) => {
self.check_attr_crate_level(attr_span, style, meta, hir_id);
self.check_doc_attr_no_value(meta, hir_id);
}
Some(sym::auto_cfg) => {
self.check_doc_auto_cfg(i_meta, hir_id);
}
Some(sym::inline | sym::no_inline) => {
self.check_doc_inline(style, meta, hir_id, target, specified_inline)
}
Some(sym::masked) => self.check_doc_masked(style, meta, hir_id, target),
Some(sym::cfg | sym::hidden | sym::notable_trait) => {}
Some(sym::rust_logo) => {
if self.check_attr_crate_level(attr_span, style, meta, hir_id)
&& !self.tcx.features().rustdoc_internals()
{
feature_err(
&self.tcx.sess,
sym::rustdoc_internals,
meta.span(),
fluent::passes_doc_rust_logo,
)
.emit();
}
}
_ => {
let path = rustc_ast_pretty::pprust::path_to_string(&i_meta.path);
if i_meta.has_name(sym::spotlight) {
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
i_meta.span,
errors::DocTestUnknownSpotlight { path, span: i_meta.span },
);
} else if i_meta.has_name(sym::include)
&& let Some(value) = i_meta.value_str()
{
let applicability = if list.len() == 1 {
Applicability::MachineApplicable
} else {
Applicability::MaybeIncorrect
};
// If there are multiple attributes, the suggestion would suggest
// deleting all of them, which is incorrect.
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
i_meta.span,
errors::DocTestUnknownInclude {
path,
value: value.to_string(),
inner: match style {
AttrStyle::Inner => "!",
AttrStyle::Outer => "",
},
sugg: (attr.span(), applicability),
},
);
} else if i_meta.has_name(sym::passes)
|| i_meta.has_name(sym::no_default_passes)
{
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
i_meta.span,
errors::DocTestUnknownPasses { path, span: i_meta.span },
);
} else if i_meta.has_name(sym::plugins) {
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
i_meta.span,
errors::DocTestUnknownPlugins { path, span: i_meta.span },
);
} else {
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
i_meta.span,
errors::DocTestUnknownAny { path },
);
}
}
}
} else {
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
hir_id,
meta.span(),
errors::DocInvalid,
);
}
for (alias, span) in aliases {
if self.check_attr_not_crate_level(*span, hir_id, "alias") {
self.check_doc_alias_value(*span, hir_id, target, *alias);
}
}
if let Some((_, span)) = keyword
&& self.check_attr_not_crate_level(*span, hir_id, "keyword")
{
self.check_doc_keyword_and_attribute(*span, hir_id, "keyword");
}
if let Some((_, span)) = attribute
&& self.check_attr_not_crate_level(*span, hir_id, "attribute")
{
self.check_doc_keyword_and_attribute(*span, hir_id, "attribute");
}
if let Some(span) = fake_variadic
&& self.check_attr_not_crate_level(*span, hir_id, "fake_variadic")
{
self.check_doc_fake_variadic(*span, hir_id);
}
if let Some(span) = search_unbox
&& self.check_attr_not_crate_level(*span, hir_id, "search_unbox")
{
self.check_doc_search_unbox(*span, hir_id);
}
for i in [
html_favicon_url,
html_logo_url,
html_playground_url,
issue_tracker_base_url,
html_root_url,
] {
if let Some((_, span)) = i {
self.check_attr_crate_level(*span, hir_id);
}
}
for i in [html_no_source, no_crate_inject] {
if let Some(span) = i {
self.check_attr_crate_level(*span, hir_id);
}
}
self.check_doc_inline(hir_id, target, inline);
if let Some(span) = rust_logo
&& self.check_attr_crate_level(*span, hir_id)
&& !self.tcx.features().rustdoc_internals()
{
feature_err(
&self.tcx.sess,
sym::rustdoc_internals,
*span,
fluent::passes_doc_rust_logo,
)
.emit();
}
if let Some(span) = masked {
self.check_doc_masked(*span, hir_id, target);
}
}
fn check_has_incoherent_inherent_impls(&self, attr: &Attribute, span: Span, target: Target) {
@ -2354,7 +1985,14 @@ impl<'tcx> Visitor<'tcx> for CheckAttrVisitor<'tcx> {
.hir_attrs(where_predicate.hir_id)
.iter()
.filter(|attr| !ATTRS_ALLOWED.iter().any(|&sym| attr.has_name(sym)))
.filter(|attr| !attr.is_parsed_attr())
// FIXME: We shouldn't need to special-case `doc`!
.filter(|attr| {
matches!(
attr,
Attribute::Parsed(AttributeKind::DocComment { .. } | AttributeKind::Doc(_))
| Attribute::Unparsed(_)
)
})
.map(|attr| attr.span())
.collect::<Vec<_>>();
if !spans.is_empty() {

View file

@ -24,18 +24,6 @@ pub(crate) struct IncorrectDoNotRecommendLocation;
#[diag(passes_incorrect_do_not_recommend_args)]
pub(crate) struct DoNotRecommendDoesNotExpectArgs;
#[derive(LintDiagnostic)]
#[diag(passes_doc_attr_expects_string)]
pub(crate) struct DocAttrExpectsString {
pub(crate) attr_name: Symbol,
}
#[derive(LintDiagnostic)]
#[diag(passes_doc_attr_expects_no_value)]
pub(crate) struct DocAttrExpectsNoValue {
pub(crate) attr_name: Symbol,
}
#[derive(Diagnostic)]
#[diag(passes_autodiff_attr)]
pub(crate) struct AutoDiffAttr {
@ -135,75 +123,20 @@ pub(crate) struct AttrShouldBeAppliedToStatic {
pub defn_span: Span,
}
#[derive(Diagnostic)]
#[diag(passes_doc_expect_str)]
pub(crate) struct DocExpectStr<'a> {
#[primary_span]
pub attr_span: Span,
pub attr_name: &'a str,
}
#[derive(Diagnostic)]
#[diag(passes_doc_alias_empty)]
pub(crate) struct DocAliasEmpty<'a> {
#[primary_span]
pub span: Span,
pub attr_str: &'a str,
}
#[derive(Diagnostic)]
#[diag(passes_doc_alias_bad_char)]
pub(crate) struct DocAliasBadChar<'a> {
#[primary_span]
pub span: Span,
pub attr_str: &'a str,
pub char_: char,
}
#[derive(Diagnostic)]
#[diag(passes_doc_alias_start_end)]
pub(crate) struct DocAliasStartEnd<'a> {
#[primary_span]
pub span: Span,
pub attr_str: &'a str,
}
#[derive(Diagnostic)]
#[diag(passes_doc_alias_bad_location)]
pub(crate) struct DocAliasBadLocation<'a> {
#[primary_span]
pub span: Span,
pub attr_str: &'a str,
pub location: &'a str,
}
#[derive(Diagnostic)]
#[diag(passes_doc_alias_not_an_alias)]
pub(crate) struct DocAliasNotAnAlias<'a> {
#[primary_span]
pub span: Span,
pub attr_str: &'a str,
}
#[derive(LintDiagnostic)]
#[diag(passes_doc_alias_duplicated)]
pub(crate) struct DocAliasDuplicated {
#[label]
pub first_defn: Span,
}
#[derive(Diagnostic)]
#[diag(passes_doc_alias_not_string_literal)]
pub(crate) struct DocAliasNotStringLiteral {
#[primary_span]
pub span: Span,
}
#[derive(Diagnostic)]
#[diag(passes_doc_alias_malformed)]
pub(crate) struct DocAliasMalformed {
pub(crate) struct DocAliasNotAnAlias {
#[primary_span]
pub span: Span,
pub attr_str: Symbol,
}
#[derive(Diagnostic)]
@ -214,24 +147,6 @@ pub(crate) struct DocKeywordAttributeEmptyMod {
pub attr_name: &'static str,
}
#[derive(Diagnostic)]
#[diag(passes_doc_keyword_not_keyword)]
#[help]
pub(crate) struct DocKeywordNotKeyword {
#[primary_span]
pub span: Span,
pub keyword: Symbol,
}
#[derive(Diagnostic)]
#[diag(passes_doc_attribute_not_attribute)]
#[help]
pub(crate) struct DocAttributeNotAttribute {
#[primary_span]
pub span: Span,
pub attribute: Symbol,
}
#[derive(Diagnostic)]
#[diag(passes_doc_keyword_attribute_not_mod)]
pub(crate) struct DocKeywordAttributeNotMod {
@ -264,7 +179,7 @@ pub(crate) struct DocSearchUnboxInvalid {
#[derive(Diagnostic)]
#[diag(passes_doc_inline_conflict)]
#[help]
pub(crate) struct DocKeywordConflict {
pub(crate) struct DocInlineConflict {
#[primary_span]
pub spans: MultiSpan,
}
@ -276,7 +191,7 @@ pub(crate) struct DocInlineOnlyUse {
#[label]
pub attr_span: Span,
#[label(passes_not_a_use_item_label)]
pub item_span: Option<Span>,
pub item_span: Span,
}
#[derive(LintDiagnostic)]
@ -286,7 +201,7 @@ pub(crate) struct DocMaskedOnlyExternCrate {
#[label]
pub attr_span: Span,
#[label(passes_not_an_extern_crate_label)]
pub item_span: Option<Span>,
pub item_span: Span,
}
#[derive(LintDiagnostic)]
@ -295,7 +210,7 @@ pub(crate) struct DocMaskedNotExternCrateSelf {
#[label]
pub attr_span: Span,
#[label(passes_extern_crate_self_label)]
pub item_span: Option<Span>,
pub item_span: Span,
}
#[derive(Diagnostic)]
@ -306,90 +221,6 @@ pub(crate) struct DocAttrNotCrateLevel<'a> {
pub attr_name: &'a str,
}
#[derive(LintDiagnostic)]
#[diag(passes_doc_test_unknown)]
pub(crate) struct DocTestUnknown {
pub path: String,
}
#[derive(LintDiagnostic)]
#[diag(passes_doc_test_literal)]
pub(crate) struct DocTestLiteral;
#[derive(LintDiagnostic)]
#[diag(passes_doc_test_takes_list)]
pub(crate) struct DocTestTakesList;
#[derive(LintDiagnostic)]
#[diag(passes_doc_auto_cfg_wrong_literal)]
pub(crate) struct DocAutoCfgWrongLiteral;
#[derive(LintDiagnostic)]
#[diag(passes_doc_auto_cfg_expects_hide_or_show)]
pub(crate) struct DocAutoCfgExpectsHideOrShow;
#[derive(LintDiagnostic)]
#[diag(passes_doc_auto_cfg_hide_show_expects_list)]
pub(crate) struct DocAutoCfgHideShowExpectsList {
pub attr_name: Symbol,
}
#[derive(LintDiagnostic)]
#[diag(passes_doc_auto_cfg_hide_show_unexpected_item)]
pub(crate) struct DocAutoCfgHideShowUnexpectedItem {
pub attr_name: Symbol,
}
#[derive(LintDiagnostic)]
#[diag(passes_doc_test_unknown_any)]
pub(crate) struct DocTestUnknownAny {
pub path: String,
}
#[derive(LintDiagnostic)]
#[diag(passes_doc_test_unknown_spotlight)]
#[note]
#[note(passes_no_op_note)]
pub(crate) struct DocTestUnknownSpotlight {
pub path: String,
#[suggestion(style = "short", applicability = "machine-applicable", code = "notable_trait")]
pub span: Span,
}
#[derive(LintDiagnostic)]
#[diag(passes_doc_test_unknown_passes)]
#[note]
#[note(passes_no_op_note)]
pub(crate) struct DocTestUnknownPasses {
pub path: String,
#[label]
pub span: Span,
}
#[derive(LintDiagnostic)]
#[diag(passes_doc_test_unknown_plugins)]
#[note]
#[note(passes_no_op_note)]
pub(crate) struct DocTestUnknownPlugins {
pub path: String,
#[label]
pub span: Span,
}
#[derive(LintDiagnostic)]
#[diag(passes_doc_test_unknown_include)]
pub(crate) struct DocTestUnknownInclude {
pub path: String,
pub value: String,
pub inner: &'static str,
#[suggestion(code = "#{inner}[doc = include_str!(\"{value}\")]")]
pub sugg: (Span, Applicability),
}
#[derive(LintDiagnostic)]
#[diag(passes_doc_invalid)]
pub(crate) struct DocInvalid;
#[derive(Diagnostic)]
#[diag(passes_has_incoherent_inherent_impl)]
pub(crate) struct HasIncoherentInherentImpl {
@ -1353,17 +1184,7 @@ pub(crate) struct IneffectiveUnstableImpl;
#[derive(LintDiagnostic)]
#[diag(passes_attr_crate_level)]
#[note]
pub(crate) struct AttrCrateLevelOnly {
#[subdiagnostic]
pub sugg: Option<AttrCrateLevelOnlySugg>,
}
#[derive(Subdiagnostic)]
#[suggestion(passes_suggestion, applicability = "maybe-incorrect", code = "!", style = "verbose")]
pub(crate) struct AttrCrateLevelOnlySugg {
#[primary_span]
pub attr: Span,
}
pub(crate) struct AttrCrateLevelOnly {}
/// "sanitize attribute not allowed here"
#[derive(Diagnostic)]

View file

@ -1222,7 +1222,7 @@ impl<'ast, 'ra, 'tcx> Visitor<'ast> for LateResolutionVisitor<'_, 'ast, 'ra, 'tc
if let TyKind::Path(None, ref path) = ty.kind
// We cannot disambiguate multi-segment paths right now as that requires type
// checking.
&& path.is_potential_trivial_const_arg(false)
&& path.is_potential_trivial_const_arg()
{
let mut check_ns = |ns| {
self.maybe_resolve_ident_in_lexical_scope(path.segments[0].ident, ns)
@ -4840,9 +4840,12 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
constant, anon_const_kind
);
let is_trivial_const_arg = constant
.value
.is_potential_trivial_const_arg(self.r.tcx.features().min_generic_const_args());
let is_trivial_const_arg = if self.r.tcx.features().min_generic_const_args() {
matches!(constant.mgca_disambiguation, MgcaDisambiguation::Direct)
} else {
constant.value.is_potential_trivial_const_arg()
};
self.resolve_anon_const_manual(is_trivial_const_arg, anon_const_kind, |this| {
this.resolve_expr(&constant.value, None)
})
@ -5023,9 +5026,9 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
// Constant arguments need to be treated as AnonConst since
// that is how they will be later lowered to HIR.
if const_args.contains(&idx) {
let is_trivial_const_arg = argument.is_potential_trivial_const_arg(
self.r.tcx.features().min_generic_const_args(),
);
// FIXME(mgca): legacy const generics doesn't support mgca but maybe
// that's okay.
let is_trivial_const_arg = argument.is_potential_trivial_const_arg();
self.resolve_anon_const_manual(
is_trivial_const_arg,
AnonConstKind::ConstArg(IsRepeatExpr::No),

View file

@ -10,7 +10,6 @@ use rustc_ast::{
Item, ItemKind, MethodCall, NodeId, Path, PathSegment, Ty, TyKind,
};
use rustc_ast_pretty::pprust::where_bound_predicate_to_string;
use rustc_attr_parsing::is_doc_alias_attrs_contain_symbol;
use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
use rustc_errors::codes::*;
use rustc_errors::{
@ -18,6 +17,7 @@ use rustc_errors::{
struct_span_code_err,
};
use rustc_hir as hir;
use rustc_hir::attrs::AttributeKind;
use rustc_hir::def::Namespace::{self, *};
use rustc_hir::def::{self, CtorKind, CtorOf, DefKind, MacroKinds};
use rustc_hir::def_id::{CRATE_DEF_ID, DefId};
@ -903,7 +903,10 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
// confused by them.
continue;
}
if is_doc_alias_attrs_contain_symbol(r.tcx.get_attrs(did, sym::doc), item_name) {
if let Some(d) =
hir::find_attr!(r.tcx.get_all_attrs(did), AttributeKind::Doc(d) => d)
&& d.aliases.contains_key(&item_name)
{
return Some(did);
}
}

View file

@ -73,6 +73,7 @@ use rustc_middle::ty::{
ResolverGlobalCtxt, TyCtxt, TyCtxtFeed, Visibility,
};
use rustc_query_system::ich::StableHashingContext;
use rustc_session::config::CrateType;
use rustc_session::lint::builtin::PRIVATE_MACRO_USE;
use rustc_span::hygiene::{ExpnId, LocalExpnId, MacroKind, SyntaxContext, Transparency};
use rustc_span::{DUMMY_SP, Ident, Macros20NormalizedIdent, Span, Symbol, kw, sym};
@ -2430,6 +2431,12 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
}
fn resolve_main(&mut self) {
let any_exe = self.tcx.crate_types().contains(&CrateType::Executable);
// Don't try to resolve main unless it's an executable
if !any_exe {
return;
}
let module = self.graph_root;
let ident = Ident::with_dummy_span(sym::main);
let parent_scope = &ParentScope::module(module, self.arenas);

View file

@ -10,6 +10,7 @@ use pulldown_cmark::{
use rustc_ast as ast;
use rustc_ast::attr::AttributeExt;
use rustc_ast::join_path_syms;
use rustc_ast::token::DocFragmentKind;
use rustc_ast::util::comments::beautify_doc_string;
use rustc_data_structures::fx::FxIndexMap;
use rustc_data_structures::unord::UnordSet;
@ -23,14 +24,6 @@ use tracing::{debug, trace};
#[cfg(test)]
mod tests;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum DocFragmentKind {
/// A doc fragment created from a `///` or `//!` doc comment.
SugaredDoc,
/// A doc fragment created from a "raw" `#[doc=""]` attribute.
RawDoc,
}
/// A portion of documentation, extracted from a `#[doc]` attribute.
///
/// Each variant contains the line number within the complete doc-comment where the fragment
@ -125,7 +118,7 @@ pub fn unindent_doc_fragments(docs: &mut [DocFragment]) {
//
// In this case, you want "hello! another" and not "hello! another".
let add = if docs.windows(2).any(|arr| arr[0].kind != arr[1].kind)
&& docs.iter().any(|d| d.kind == DocFragmentKind::SugaredDoc)
&& docs.iter().any(|d| d.kind.is_sugared())
{
// In case we have a mix of sugared doc comments and "raw" ones, we want the sugared one to
// "decide" how much the minimum indent will be.
@ -155,8 +148,7 @@ pub fn unindent_doc_fragments(docs: &mut [DocFragment]) {
// Compare against either space or tab, ignoring whether they are
// mixed or not.
let whitespace = line.chars().take_while(|c| *c == ' ' || *c == '\t').count();
whitespace
+ (if fragment.kind == DocFragmentKind::SugaredDoc { 0 } else { add })
whitespace + (if fragment.kind.is_sugared() { 0 } else { add })
})
.min()
.unwrap_or(usize::MAX)
@ -171,7 +163,7 @@ pub fn unindent_doc_fragments(docs: &mut [DocFragment]) {
continue;
}
let indent = if fragment.kind != DocFragmentKind::SugaredDoc && min_indent > 0 {
let indent = if !fragment.kind.is_sugared() && min_indent > 0 {
min_indent - add
} else {
min_indent
@ -214,19 +206,17 @@ pub fn attrs_to_doc_fragments<'a, A: AttributeExt + Clone + 'a>(
let mut doc_fragments = Vec::with_capacity(size_hint);
let mut other_attrs = ThinVec::<A>::with_capacity(if doc_only { 0 } else { size_hint });
for (attr, item_id) in attrs {
if let Some((doc_str, comment_kind)) = attr.doc_str_and_comment_kind() {
let doc = beautify_doc_string(doc_str, comment_kind);
let (span, kind, from_expansion) = if let Some(span) = attr.is_doc_comment() {
(span, DocFragmentKind::SugaredDoc, span.from_expansion())
} else {
let attr_span = attr.span();
let (span, from_expansion) = match attr.value_span() {
Some(sp) => (sp.with_ctxt(attr_span.ctxt()), sp.from_expansion()),
None => (attr_span, attr_span.from_expansion()),
};
(span, DocFragmentKind::RawDoc, from_expansion)
if let Some((doc_str, fragment_kind)) = attr.doc_str_and_fragment_kind() {
let doc = beautify_doc_string(doc_str, fragment_kind.comment_kind());
let attr_span = attr.span();
let (span, from_expansion) = match fragment_kind {
DocFragmentKind::Sugared(_) => (attr_span, attr_span.from_expansion()),
DocFragmentKind::Raw(value_span) => {
(value_span.with_ctxt(attr_span.ctxt()), value_span.from_expansion())
}
};
let fragment = DocFragment { span, doc, kind, item_id, indent: 0, from_expansion };
let fragment =
DocFragment { span, doc, kind: fragment_kind, item_id, indent: 0, from_expansion };
doc_fragments.push(fragment);
} else if !doc_only {
other_attrs.push(attr.clone());
@ -377,16 +367,8 @@ pub fn inner_docs(attrs: &[impl AttributeExt]) -> bool {
/// Has `#[rustc_doc_primitive]` or `#[doc(keyword)]` or `#[doc(attribute)]`.
pub fn has_primitive_or_keyword_or_attribute_docs(attrs: &[impl AttributeExt]) -> bool {
for attr in attrs {
if attr.has_name(sym::rustc_doc_primitive) {
if attr.has_name(sym::rustc_doc_primitive) || attr.is_doc_keyword_or_attribute() {
return true;
} else if attr.has_name(sym::doc)
&& let Some(items) = attr.meta_item_list()
{
for item in items {
if item.has_name(sym::keyword) || item.has_name(sym::attribute) {
return true;
}
}
}
}
false
@ -571,7 +553,7 @@ pub fn source_span_for_markdown_range_inner(
use rustc_span::BytePos;
if let &[fragment] = &fragments
&& fragment.kind == DocFragmentKind::RawDoc
&& !fragment.kind.is_sugared()
&& let Ok(snippet) = map.span_to_snippet(fragment.span)
&& snippet.trim_end() == markdown.trim_end()
&& let Ok(md_range_lo) = u32::try_from(md_range.start)
@ -589,7 +571,7 @@ pub fn source_span_for_markdown_range_inner(
));
}
let is_all_sugared_doc = fragments.iter().all(|frag| frag.kind == DocFragmentKind::SugaredDoc);
let is_all_sugared_doc = fragments.iter().all(|frag| frag.kind.is_sugared());
if !is_all_sugared_doc {
// This case ignores the markdown outside of the range so that it can

View file

@ -2,7 +2,7 @@ use std::path::PathBuf;
use rustc_span::source_map::{FilePathMapping, SourceMap};
use rustc_span::symbol::sym;
use rustc_span::{BytePos, Span};
use rustc_span::{BytePos, DUMMY_SP, Span};
use super::{DocFragment, DocFragmentKind, source_span_for_markdown_range_inner};
@ -17,7 +17,7 @@ fn single_backtick() {
&[DocFragment {
span: Span::with_root_ctxt(BytePos(8), BytePos(11)),
item_id: None,
kind: DocFragmentKind::RawDoc,
kind: DocFragmentKind::Raw(DUMMY_SP),
doc: sym::empty, // unused placeholder
indent: 0,
from_expansion: false,
@ -40,7 +40,7 @@ fn utf8() {
&[DocFragment {
span: Span::with_root_ctxt(BytePos(8), BytePos(14)),
item_id: None,
kind: DocFragmentKind::RawDoc,
kind: DocFragmentKind::Raw(DUMMY_SP),
doc: sym::empty, // unused placeholder
indent: 0,
from_expansion: false,

View file

@ -591,7 +591,7 @@ impl () {}
/// # pub unsafe fn malloc(_size: usize) -> *mut core::ffi::c_void { core::ptr::NonNull::dangling().as_ptr() }
/// # pub unsafe fn free(_ptr: *mut core::ffi::c_void) {}
/// # }
/// # #[cfg(any())]
/// # #[cfg(false)]
/// #[allow(unused_extern_crates)]
/// extern crate libc;
///

View file

@ -13,6 +13,9 @@ fn main() {
println!("cargo:rustc-cfg=netbsd10");
}
// Needed for `#![doc(auto_cfg(hide(no_global_oom_handling)))]` attribute.
println!("cargo::rustc-check-cfg=cfg(no_global_oom_handling)");
println!("cargo:rustc-check-cfg=cfg(restricted_std)");
if target_os == "linux"
|| target_os == "android"

View file

@ -892,8 +892,8 @@ impl Step for Std {
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct RustcDev {
/// The compiler that will build rustc which will be shipped in this component.
build_compiler: Compiler,
target: TargetSelection,
pub build_compiler: Compiler,
pub target: TargetSelection,
}
impl RustcDev {

View file

@ -279,6 +279,17 @@ install!((self, builder, _config),
});
install_sh(builder, "rustc", self.build_compiler, Some(self.target), &tarball);
};
RustcDev, alias = "rustc-dev", Self::should_build(_config), IS_HOST: true, {
if let Some(tarball) = builder.ensure(dist::RustcDev {
build_compiler: self.build_compiler, target: self.target
}) {
install_sh(builder, "rustc-dev", self.build_compiler, Some(self.target), &tarball);
} else {
builder.info(
&format!("skipping Install RustcDev stage{} ({})", self.build_compiler.stage + 1, self.target),
);
}
};
RustcCodegenCranelift, alias = "rustc-codegen-cranelift", Self::should_build(_config), IS_HOST: true, {
if let Some(tarball) = builder.ensure(dist::CraneliftCodegenBackend {
compilers: RustcPrivateCompilers::from_build_compiler(builder, self.build_compiler, self.target),

View file

@ -998,6 +998,7 @@ impl<'a> Builder<'a> {
// binary path, we must install rustc before the tools. Otherwise, the rust-installer will
// install the same binaries twice for each tool, leaving backup files (*.old) as a result.
install::Rustc,
install::RustcDev,
install::Cargo,
install::RustAnalyzer,
install::Rustfmt,

View file

@ -2912,6 +2912,7 @@ mod snapshot {
[build] rustc 1 <x86_64-unknown-linux-gnu> -> rust-analyzer-proc-macro-srv 2 <x86_64-unknown-linux-gnu>
[build] rustc 0 <x86_64-unknown-linux-gnu> -> GenerateCopyright 1 <x86_64-unknown-linux-gnu>
[dist] rustc <x86_64-unknown-linux-gnu>
[dist] rustc 1 <x86_64-unknown-linux-gnu> -> rustc-dev 2 <x86_64-unknown-linux-gnu>
[build] rustc 1 <x86_64-unknown-linux-gnu> -> cargo 2 <x86_64-unknown-linux-gnu>
[dist] rustc 1 <x86_64-unknown-linux-gnu> -> cargo 2 <x86_64-unknown-linux-gnu>
[build] rustc 1 <x86_64-unknown-linux-gnu> -> rust-analyzer 2 <x86_64-unknown-linux-gnu>

View file

@ -1,4 +1,4 @@
FROM ubuntu:22.04
FROM ghcr.io/rust-lang/ubuntu:24.04
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends \

View file

@ -1,4 +1,4 @@
FROM ghcr.io/rust-lang/ubuntu:22.04
FROM ghcr.io/rust-lang/ubuntu:24.04
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends \

View file

@ -15,6 +15,7 @@ base64 = "0.21.7"
indexmap = { version = "2", features = ["serde"] }
itertools = "0.12"
minifier = { version = "0.3.5", default-features = false }
proc-macro2 = "1.0.103"
pulldown-cmark-escape = { version = "0.11.0", features = ["simd"] }
regex = "1"
rustdoc-json-types = { path = "../rustdoc-json-types" }

View file

@ -9,12 +9,13 @@ use std::{fmt, mem, ops};
use itertools::Either;
use rustc_ast::{LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit};
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_hir::attrs::AttributeKind;
use rustc_data_structures::thin_vec::{ThinVec, thin_vec};
use rustc_hir as hir;
use rustc_hir::Attribute;
use rustc_hir::attrs::{self, AttributeKind, CfgEntry, CfgHideShow, HideOrShow};
use rustc_middle::ty::TyCtxt;
use rustc_session::parse::ParseSess;
use rustc_span::Span;
use rustc_span::symbol::{Symbol, sym};
use {rustc_ast as ast, rustc_hir as hir};
use rustc_span::{DUMMY_SP, Span};
use crate::display::{Joined as _, MaybeDisplay, Wrapped};
use crate::html::escape::Escape;
@ -22,21 +23,11 @@ use crate::html::escape::Escape;
#[cfg(test)]
mod tests;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub(crate) enum Cfg {
/// Accepts all configurations.
True,
/// Denies all configurations.
False,
/// A generic configuration option, e.g., `test` or `target_os = "linux"`.
Cfg(Symbol, Option<Symbol>),
/// Negates a configuration requirement, i.e., `not(x)`.
Not(Box<Cfg>),
/// Union of a list of configuration requirements, i.e., `any(...)`.
Any(Vec<Cfg>),
/// Intersection of a list of configuration requirements, i.e., `all(...)`.
All(Vec<Cfg>),
}
#[derive(Clone, Debug, Hash)]
// Because `CfgEntry` includes `Span`, we must NEVER use `==`/`!=` operators on `Cfg` and instead
// use `is_equivalent_to`.
#[cfg_attr(test, derive(PartialEq))]
pub(crate) struct Cfg(CfgEntry);
#[derive(PartialEq, Debug)]
pub(crate) struct InvalidCfgError {
@ -44,27 +35,95 @@ pub(crate) struct InvalidCfgError {
pub(crate) span: Span,
}
/// Whether the configuration consists of just `Cfg` or `Not`.
fn is_simple_cfg(cfg: &CfgEntry) -> bool {
match cfg {
CfgEntry::Bool(..)
| CfgEntry::NameValue { .. }
| CfgEntry::Not(..)
| CfgEntry::Version(..) => true,
CfgEntry::All(..) | CfgEntry::Any(..) => false,
}
}
/// Returns `false` if is `Any`, otherwise returns `true`.
fn is_all_cfg(cfg: &CfgEntry) -> bool {
match cfg {
CfgEntry::Bool(..)
| CfgEntry::NameValue { .. }
| CfgEntry::Not(..)
| CfgEntry::Version(..)
| CfgEntry::All(..) => true,
CfgEntry::Any(..) => false,
}
}
fn strip_hidden(cfg: &CfgEntry, hidden: &FxHashSet<NameValueCfg>) -> Option<CfgEntry> {
match cfg {
CfgEntry::Bool(..) => Some(cfg.clone()),
CfgEntry::NameValue { .. } => {
if !hidden.contains(&NameValueCfg::from(cfg)) {
Some(cfg.clone())
} else {
None
}
}
CfgEntry::Not(cfg, _) => {
if let Some(cfg) = strip_hidden(cfg, hidden) {
Some(CfgEntry::Not(Box::new(cfg), DUMMY_SP))
} else {
None
}
}
CfgEntry::Any(cfgs, _) => {
let cfgs =
cfgs.iter().filter_map(|cfg| strip_hidden(cfg, hidden)).collect::<ThinVec<_>>();
if cfgs.is_empty() { None } else { Some(CfgEntry::Any(cfgs, DUMMY_SP)) }
}
CfgEntry::All(cfgs, _) => {
let cfgs =
cfgs.iter().filter_map(|cfg| strip_hidden(cfg, hidden)).collect::<ThinVec<_>>();
if cfgs.is_empty() { None } else { Some(CfgEntry::All(cfgs, DUMMY_SP)) }
}
CfgEntry::Version(..) => {
// FIXME: Should be handled.
Some(cfg.clone())
}
}
}
fn should_capitalize_first_letter(cfg: &CfgEntry) -> bool {
match cfg {
CfgEntry::Bool(..) | CfgEntry::Not(..) | CfgEntry::Version(..) => true,
CfgEntry::Any(sub_cfgs, _) | CfgEntry::All(sub_cfgs, _) => {
sub_cfgs.first().map(should_capitalize_first_letter).unwrap_or(false)
}
CfgEntry::NameValue { name, .. } => {
*name == sym::debug_assertions || *name == sym::target_endian
}
}
}
impl Cfg {
/// Parses a `MetaItemInner` into a `Cfg`.
fn parse_nested(
nested_cfg: &MetaItemInner,
exclude: &FxHashSet<Cfg>,
exclude: &FxHashSet<NameValueCfg>,
) -> Result<Option<Cfg>, InvalidCfgError> {
match nested_cfg {
MetaItemInner::MetaItem(cfg) => Cfg::parse_without(cfg, exclude),
MetaItemInner::Lit(MetaItemLit { kind: LitKind::Bool(b), .. }) => match *b {
true => Ok(Some(Cfg::True)),
false => Ok(Some(Cfg::False)),
},
MetaItemInner::Lit(MetaItemLit { kind: LitKind::Bool(b), .. }) => {
Ok(Some(Cfg(CfgEntry::Bool(*b, DUMMY_SP))))
}
MetaItemInner::Lit(lit) => {
Err(InvalidCfgError { msg: "unexpected literal", span: lit.span })
}
}
}
pub(crate) fn parse_without(
fn parse_without(
cfg: &MetaItem,
exclude: &FxHashSet<Cfg>,
exclude: &FxHashSet<NameValueCfg>,
) -> Result<Option<Cfg>, InvalidCfgError> {
let name = match cfg.ident() {
Some(ident) => ident.name,
@ -77,13 +136,23 @@ impl Cfg {
};
match cfg.kind {
MetaItemKind::Word => {
let cfg = Cfg::Cfg(name, None);
if exclude.contains(&cfg) { Ok(None) } else { Ok(Some(cfg)) }
if exclude.contains(&NameValueCfg::new(name)) {
Ok(None)
} else {
Ok(Some(Cfg(CfgEntry::NameValue { name, value: None, span: DUMMY_SP })))
}
}
MetaItemKind::NameValue(ref lit) => match lit.kind {
LitKind::Str(value, _) => {
let cfg = Cfg::Cfg(name, Some(value));
if exclude.contains(&cfg) { Ok(None) } else { Ok(Some(cfg)) }
if exclude.contains(&NameValueCfg::new_value(name, value)) {
Ok(None)
} else {
Ok(Some(Cfg(CfgEntry::NameValue {
name,
value: Some(value),
span: DUMMY_SP,
})))
}
}
_ => Err(InvalidCfgError {
// FIXME: if the main #[cfg] syntax decided to support non-string literals,
@ -97,8 +166,12 @@ impl Cfg {
let mut sub_cfgs =
items.iter().filter_map(|i| Cfg::parse_nested(i, exclude).transpose());
let ret = match name {
sym::all => sub_cfgs.try_fold(Cfg::True, |x, y| Ok(x & y?)),
sym::any => sub_cfgs.try_fold(Cfg::False, |x, y| Ok(x | y?)),
sym::all => {
sub_cfgs.try_fold(Cfg(CfgEntry::Bool(true, DUMMY_SP)), |x, y| Ok(x & y?))
}
sym::any => {
sub_cfgs.try_fold(Cfg(CfgEntry::Bool(false, DUMMY_SP)), |x, y| Ok(x | y?))
}
sym::not => {
if orig_len == 1 {
let mut sub_cfgs = sub_cfgs.collect::<Vec<_>>();
@ -132,40 +205,10 @@ impl Cfg {
Self::parse_nested(cfg, &FxHashSet::default()).map(|ret| ret.unwrap())
}
/// Checks whether the given configuration can be matched in the current session.
///
/// Equivalent to `attr::cfg_matches`.
pub(crate) fn matches(&self, psess: &ParseSess) -> bool {
match *self {
Cfg::False => false,
Cfg::True => true,
Cfg::Not(ref child) => !child.matches(psess),
Cfg::All(ref sub_cfgs) => sub_cfgs.iter().all(|sub_cfg| sub_cfg.matches(psess)),
Cfg::Any(ref sub_cfgs) => sub_cfgs.iter().any(|sub_cfg| sub_cfg.matches(psess)),
Cfg::Cfg(name, value) => psess.config.contains(&(name, value)),
}
}
/// Whether the configuration consists of just `Cfg` or `Not`.
fn is_simple(&self) -> bool {
match self {
Cfg::False | Cfg::True | Cfg::Cfg(..) | Cfg::Not(..) => true,
Cfg::All(..) | Cfg::Any(..) => false,
}
}
/// Whether the configuration consists of just `Cfg`, `Not` or `All`.
fn is_all(&self) -> bool {
match self {
Cfg::False | Cfg::True | Cfg::Cfg(..) | Cfg::Not(..) | Cfg::All(..) => true,
Cfg::Any(..) => false,
}
}
/// Renders the configuration for human display, as a short HTML description.
pub(crate) fn render_short_html(&self) -> String {
let mut msg = Display(self, Format::ShortHtml).to_string();
if self.should_capitalize_first_letter()
let mut msg = Display(&self.0, Format::ShortHtml).to_string();
if should_capitalize_first_letter(&self.0)
&& let Some(i) = msg.find(|c: char| c.is_ascii_alphanumeric())
{
msg[i..i + 1].make_ascii_uppercase();
@ -183,9 +226,9 @@ impl Cfg {
};
let mut msg = if matches!(format, Format::LongHtml) {
format!("Available{on}<strong>{}</strong>", Display(self, format))
format!("Available{on}<strong>{}</strong>", Display(&self.0, format))
} else {
format!("Available{on}{}", Display(self, format))
format!("Available{on}{}", Display(&self.0, format))
};
if self.should_append_only_to_description() {
msg.push_str(" only");
@ -205,27 +248,19 @@ impl Cfg {
self.render_long_inner(Format::LongPlain)
}
fn should_capitalize_first_letter(&self) -> bool {
match *self {
Cfg::False | Cfg::True | Cfg::Not(..) => true,
Cfg::Any(ref sub_cfgs) | Cfg::All(ref sub_cfgs) => {
sub_cfgs.first().map(Cfg::should_capitalize_first_letter).unwrap_or(false)
}
Cfg::Cfg(name, _) => name == sym::debug_assertions || name == sym::target_endian,
}
}
fn should_append_only_to_description(&self) -> bool {
match self {
Cfg::False | Cfg::True => false,
Cfg::Any(..) | Cfg::All(..) | Cfg::Cfg(..) => true,
Cfg::Not(box Cfg::Cfg(..)) => true,
Cfg::Not(..) => false,
match self.0 {
CfgEntry::Any(..)
| CfgEntry::All(..)
| CfgEntry::NameValue { .. }
| CfgEntry::Version(..)
| CfgEntry::Not(box CfgEntry::NameValue { .. }, _) => true,
CfgEntry::Not(..) | CfgEntry::Bool(..) => false,
}
}
fn should_use_with_in_description(&self) -> bool {
matches!(self, Cfg::Cfg(sym::target_feature, _))
matches!(self.0, CfgEntry::NameValue { name, .. } if name == sym::target_feature)
}
/// Attempt to simplify this cfg by assuming that `assume` is already known to be true, will
@ -234,22 +269,22 @@ impl Cfg {
///
/// See `tests::test_simplify_with` for examples.
pub(crate) fn simplify_with(&self, assume: &Self) -> Option<Self> {
if self == assume {
if self.0.is_equivalent_to(&assume.0) {
None
} else if let Cfg::All(a) = self {
let mut sub_cfgs: Vec<Cfg> = if let Cfg::All(b) = assume {
a.iter().filter(|a| !b.contains(a)).cloned().collect()
} else if let CfgEntry::All(a, _) = &self.0 {
let mut sub_cfgs: ThinVec<CfgEntry> = if let CfgEntry::All(b, _) = &assume.0 {
a.iter().filter(|a| !b.iter().any(|b| a.is_equivalent_to(b))).cloned().collect()
} else {
a.iter().filter(|&a| a != assume).cloned().collect()
a.iter().filter(|&a| !a.is_equivalent_to(&assume.0)).cloned().collect()
};
let len = sub_cfgs.len();
match len {
0 => None,
1 => sub_cfgs.pop(),
_ => Some(Cfg::All(sub_cfgs)),
1 => sub_cfgs.pop().map(Cfg),
_ => Some(Cfg(CfgEntry::All(sub_cfgs, DUMMY_SP))),
}
} else if let Cfg::All(b) = assume
&& b.contains(self)
} else if let CfgEntry::All(b, _) = &assume.0
&& b.iter().any(|b| b.is_equivalent_to(&self.0))
{
None
} else {
@ -258,81 +293,54 @@ impl Cfg {
}
fn omit_preposition(&self) -> bool {
matches!(self, Cfg::True | Cfg::False)
matches!(self.0, CfgEntry::Bool(..))
}
pub(crate) fn strip_hidden(&self, hidden: &FxHashSet<Cfg>) -> Option<Self> {
match self {
Self::True | Self::False => Some(self.clone()),
Self::Cfg(..) => {
if !hidden.contains(self) {
Some(self.clone())
} else {
None
}
}
Self::Not(cfg) => {
if let Some(cfg) = cfg.strip_hidden(hidden) {
Some(Self::Not(Box::new(cfg)))
} else {
None
}
}
Self::Any(cfgs) => {
let cfgs =
cfgs.iter().filter_map(|cfg| cfg.strip_hidden(hidden)).collect::<Vec<_>>();
if cfgs.is_empty() { None } else { Some(Self::Any(cfgs)) }
}
Self::All(cfgs) => {
let cfgs =
cfgs.iter().filter_map(|cfg| cfg.strip_hidden(hidden)).collect::<Vec<_>>();
if cfgs.is_empty() { None } else { Some(Self::All(cfgs)) }
}
}
pub(crate) fn inner(&self) -> &CfgEntry {
&self.0
}
}
impl ops::Not for Cfg {
type Output = Cfg;
fn not(self) -> Cfg {
match self {
Cfg::False => Cfg::True,
Cfg::True => Cfg::False,
Cfg::Not(cfg) => *cfg,
s => Cfg::Not(Box::new(s)),
}
Cfg(match self.0 {
CfgEntry::Bool(v, s) => CfgEntry::Bool(!v, s),
CfgEntry::Not(cfg, _) => *cfg,
s => CfgEntry::Not(Box::new(s), DUMMY_SP),
})
}
}
impl ops::BitAndAssign for Cfg {
fn bitand_assign(&mut self, other: Cfg) {
match (self, other) {
(Cfg::False, _) | (_, Cfg::True) => {}
(s, Cfg::False) => *s = Cfg::False,
(s @ Cfg::True, b) => *s = b,
(Cfg::All(a), Cfg::All(ref mut b)) => {
match (&mut self.0, other.0) {
(CfgEntry::Bool(false, _), _) | (_, CfgEntry::Bool(true, _)) => {}
(s, CfgEntry::Bool(false, _)) => *s = CfgEntry::Bool(false, DUMMY_SP),
(s @ CfgEntry::Bool(true, _), b) => *s = b,
(CfgEntry::All(a, _), CfgEntry::All(ref mut b, _)) => {
for c in b.drain(..) {
if !a.contains(&c) {
if !a.iter().any(|a| a.is_equivalent_to(&c)) {
a.push(c);
}
}
}
(Cfg::All(a), ref mut b) => {
if !a.contains(b) {
a.push(mem::replace(b, Cfg::True));
(CfgEntry::All(a, _), ref mut b) => {
if !a.iter().any(|a| a.is_equivalent_to(b)) {
a.push(mem::replace(b, CfgEntry::Bool(true, DUMMY_SP)));
}
}
(s, Cfg::All(mut a)) => {
let b = mem::replace(s, Cfg::True);
if !a.contains(&b) {
(s, CfgEntry::All(mut a, _)) => {
let b = mem::replace(s, CfgEntry::Bool(true, DUMMY_SP));
if !a.iter().any(|a| a.is_equivalent_to(&b)) {
a.push(b);
}
*s = Cfg::All(a);
*s = CfgEntry::All(a, DUMMY_SP);
}
(s, b) => {
if *s != b {
let a = mem::replace(s, Cfg::True);
*s = Cfg::All(vec![a, b]);
if !s.is_equivalent_to(&b) {
let a = mem::replace(s, CfgEntry::Bool(true, DUMMY_SP));
*s = CfgEntry::All(thin_vec![a, b], DUMMY_SP);
}
}
}
@ -349,32 +357,34 @@ impl ops::BitAnd for Cfg {
impl ops::BitOrAssign for Cfg {
fn bitor_assign(&mut self, other: Cfg) {
match (self, other) {
(Cfg::True, _) | (_, Cfg::False) | (_, Cfg::True) => {}
(s @ Cfg::False, b) => *s = b,
(Cfg::Any(a), Cfg::Any(ref mut b)) => {
match (&mut self.0, other.0) {
(CfgEntry::Bool(true, _), _)
| (_, CfgEntry::Bool(false, _))
| (_, CfgEntry::Bool(true, _)) => {}
(s @ CfgEntry::Bool(false, _), b) => *s = b,
(CfgEntry::Any(a, _), CfgEntry::Any(ref mut b, _)) => {
for c in b.drain(..) {
if !a.contains(&c) {
if !a.iter().any(|a| a.is_equivalent_to(&c)) {
a.push(c);
}
}
}
(Cfg::Any(a), ref mut b) => {
if !a.contains(b) {
a.push(mem::replace(b, Cfg::True));
(CfgEntry::Any(a, _), ref mut b) => {
if !a.iter().any(|a| a.is_equivalent_to(b)) {
a.push(mem::replace(b, CfgEntry::Bool(true, DUMMY_SP)));
}
}
(s, Cfg::Any(mut a)) => {
let b = mem::replace(s, Cfg::True);
if !a.contains(&b) {
(s, CfgEntry::Any(mut a, _)) => {
let b = mem::replace(s, CfgEntry::Bool(true, DUMMY_SP));
if !a.iter().any(|a| a.is_equivalent_to(&b)) {
a.push(b);
}
*s = Cfg::Any(a);
*s = CfgEntry::Any(a, DUMMY_SP);
}
(s, b) => {
if *s != b {
let a = mem::replace(s, Cfg::True);
*s = Cfg::Any(vec![a, b]);
if !s.is_equivalent_to(&b) {
let a = mem::replace(s, CfgEntry::Bool(true, DUMMY_SP));
*s = CfgEntry::Any(thin_vec![a, b], DUMMY_SP);
}
}
}
@ -417,7 +427,7 @@ impl Format {
}
/// Pretty-print wrapper for a `Cfg`. Also indicates what form of rendering should be used.
struct Display<'a>(&'a Cfg, Format);
struct Display<'a>(&'a CfgEntry, Format);
impl Display<'_> {
fn code_wrappers(&self) -> Wrapped<&'static str> {
@ -427,17 +437,21 @@ impl Display<'_> {
fn display_sub_cfgs(
&self,
fmt: &mut fmt::Formatter<'_>,
sub_cfgs: &[Cfg],
sub_cfgs: &[CfgEntry],
separator: &str,
) -> fmt::Result {
use fmt::Display as _;
let short_longhand = self.1.is_long() && {
let all_crate_features =
sub_cfgs.iter().all(|sub_cfg| matches!(sub_cfg, Cfg::Cfg(sym::feature, Some(_))));
let all_target_features = sub_cfgs
.iter()
.all(|sub_cfg| matches!(sub_cfg, Cfg::Cfg(sym::target_feature, Some(_))));
let all_crate_features = sub_cfgs.iter().all(|sub_cfg| {
matches!(sub_cfg, CfgEntry::NameValue { name: sym::feature, value: Some(_), .. })
});
let all_target_features = sub_cfgs.iter().all(|sub_cfg| {
matches!(
sub_cfg,
CfgEntry::NameValue { name: sym::target_feature, value: Some(_), .. }
)
});
if all_crate_features {
fmt.write_str("crate features ")?;
@ -454,14 +468,14 @@ impl Display<'_> {
sub_cfgs
.iter()
.map(|sub_cfg| {
if let Cfg::Cfg(_, Some(feat)) = sub_cfg
if let CfgEntry::NameValue { value: Some(feat), .. } = sub_cfg
&& short_longhand
{
Either::Left(self.code_wrappers().wrap(feat))
} else {
Either::Right(
Wrapped::with_parens()
.when(!sub_cfg.is_all())
.when(!is_all_cfg(sub_cfg))
.wrap(Display(sub_cfg, self.1)),
)
}
@ -476,35 +490,41 @@ impl Display<'_> {
impl fmt::Display for Display<'_> {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0 {
Cfg::Not(box Cfg::Any(sub_cfgs)) => {
let separator =
if sub_cfgs.iter().all(Cfg::is_simple) { " nor " } else { ", nor " };
match &self.0 {
CfgEntry::Not(box CfgEntry::Any(sub_cfgs, _), _) => {
let separator = if sub_cfgs.iter().all(is_simple_cfg) { " nor " } else { ", nor " };
fmt.write_str("neither ")?;
sub_cfgs
.iter()
.map(|sub_cfg| {
Wrapped::with_parens()
.when(!sub_cfg.is_all())
.when(!is_all_cfg(sub_cfg))
.wrap(Display(sub_cfg, self.1))
})
.joined(separator, fmt)
}
Cfg::Not(box simple @ Cfg::Cfg(..)) => write!(fmt, "non-{}", Display(simple, self.1)),
Cfg::Not(box c) => write!(fmt, "not ({})", Display(c, self.1)),
Cfg::Any(sub_cfgs) => {
let separator = if sub_cfgs.iter().all(Cfg::is_simple) { " or " } else { ", or " };
self.display_sub_cfgs(fmt, sub_cfgs, separator)
CfgEntry::Not(box simple @ CfgEntry::NameValue { .. }, _) => {
write!(fmt, "non-{}", Display(simple, self.1))
}
Cfg::All(sub_cfgs) => self.display_sub_cfgs(fmt, sub_cfgs, " and "),
CfgEntry::Not(box c, _) => write!(fmt, "not ({})", Display(c, self.1)),
Cfg::True => fmt.write_str("everywhere"),
Cfg::False => fmt.write_str("nowhere"),
CfgEntry::Any(sub_cfgs, _) => {
let separator = if sub_cfgs.iter().all(is_simple_cfg) { " or " } else { ", or " };
self.display_sub_cfgs(fmt, sub_cfgs.as_slice(), separator)
}
CfgEntry::All(sub_cfgs, _) => self.display_sub_cfgs(fmt, sub_cfgs.as_slice(), " and "),
&Cfg::Cfg(name, value) => {
let human_readable = match (name, value) {
CfgEntry::Bool(v, _) => {
if *v {
fmt.write_str("everywhere")
} else {
fmt.write_str("nowhere")
}
}
&CfgEntry::NameValue { name, value, .. } => {
let human_readable = match (*name, value) {
(sym::unix, None) => "Unix",
(sym::windows, None) => "Windows",
(sym::debug_assertions, None) => "debug-assertions enabled",
@ -572,8 +592,12 @@ impl fmt::Display for Display<'_> {
"sgx" => "SGX",
_ => "",
},
(sym::target_endian, Some(endian)) => return write!(fmt, "{endian}-endian"),
(sym::target_pointer_width, Some(bits)) => return write!(fmt, "{bits}-bit"),
(sym::target_endian, Some(endian)) => {
return write!(fmt, "{endian}-endian");
}
(sym::target_pointer_width, Some(bits)) => {
return write!(fmt, "{bits}-bit");
}
(sym::target_feature, Some(feat)) => match self.1 {
Format::LongHtml => {
return write!(fmt, "target feature <code>{feat}</code>");
@ -601,16 +625,52 @@ impl fmt::Display for Display<'_> {
.fmt(fmt)
}
}
CfgEntry::Version(..) => {
// FIXME: Should we handle it?
Ok(())
}
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
struct NameValueCfg {
name: Symbol,
value: Option<Symbol>,
}
impl NameValueCfg {
fn new(name: Symbol) -> Self {
Self { name, value: None }
}
fn new_value(name: Symbol, value: Symbol) -> Self {
Self { name, value: Some(value) }
}
}
impl<'a> From<&'a CfgEntry> for NameValueCfg {
fn from(cfg: &'a CfgEntry) -> Self {
match cfg {
CfgEntry::NameValue { name, value, .. } => NameValueCfg { name: *name, value: *value },
_ => NameValueCfg { name: sym::empty, value: None },
}
}
}
impl<'a> From<&'a attrs::CfgInfo> for NameValueCfg {
fn from(cfg: &'a attrs::CfgInfo) -> Self {
Self { name: cfg.name, value: cfg.value.map(|(value, _)| value) }
}
}
/// This type keeps track of (doc) cfg information as we go down the item tree.
#[derive(Clone, Debug)]
pub(crate) struct CfgInfo {
/// List of currently active `doc(auto_cfg(hide(...)))` cfgs, minus currently active
/// `doc(auto_cfg(show(...)))` cfgs.
hidden_cfg: FxHashSet<Cfg>,
hidden_cfg: FxHashSet<NameValueCfg>,
/// Current computed `cfg`. Each time we enter a new item, this field is updated as well while
/// taking into account the `hidden_cfg` information.
current_cfg: Cfg,
@ -626,11 +686,11 @@ impl Default for CfgInfo {
fn default() -> Self {
Self {
hidden_cfg: FxHashSet::from_iter([
Cfg::Cfg(sym::test, None),
Cfg::Cfg(sym::doc, None),
Cfg::Cfg(sym::doctest, None),
NameValueCfg::new(sym::test),
NameValueCfg::new(sym::doc),
NameValueCfg::new(sym::doctest),
]),
current_cfg: Cfg::True,
current_cfg: Cfg(CfgEntry::Bool(true, DUMMY_SP)),
auto_cfg_active: true,
parent_is_doc_cfg: false,
}
@ -662,33 +722,26 @@ fn show_hide_show_conflict_error(
fn handle_auto_cfg_hide_show(
tcx: TyCtxt<'_>,
cfg_info: &mut CfgInfo,
sub_attr: &MetaItemInner,
is_show: bool,
attr: &CfgHideShow,
new_show_attrs: &mut FxHashMap<(Symbol, Option<Symbol>), rustc_span::Span>,
new_hide_attrs: &mut FxHashMap<(Symbol, Option<Symbol>), rustc_span::Span>,
) {
if let MetaItemInner::MetaItem(item) = sub_attr
&& let MetaItemKind::List(items) = &item.kind
{
for item in items {
// FIXME: Report in case `Cfg::parse` reports an error?
if let Ok(Cfg::Cfg(key, value)) = Cfg::parse(item) {
if is_show {
if let Some(span) = new_hide_attrs.get(&(key, value)) {
show_hide_show_conflict_error(tcx, item.span(), *span);
} else {
new_show_attrs.insert((key, value), item.span());
}
cfg_info.hidden_cfg.remove(&Cfg::Cfg(key, value));
} else {
if let Some(span) = new_show_attrs.get(&(key, value)) {
show_hide_show_conflict_error(tcx, item.span(), *span);
} else {
new_hide_attrs.insert((key, value), item.span());
}
cfg_info.hidden_cfg.insert(Cfg::Cfg(key, value));
}
for value in &attr.values {
let simple = NameValueCfg::from(value);
if attr.kind == HideOrShow::Show {
if let Some(span) = new_hide_attrs.get(&(simple.name, simple.value)) {
show_hide_show_conflict_error(tcx, value.span_for_name_and_value(), *span);
} else {
new_show_attrs.insert((simple.name, simple.value), value.span_for_name_and_value());
}
cfg_info.hidden_cfg.remove(&simple);
} else {
if let Some(span) = new_show_attrs.get(&(simple.name, simple.value)) {
show_hide_show_conflict_error(tcx, value.span_for_name_and_value(), *span);
} else {
new_hide_attrs.insert((simple.name, simple.value), value.span_for_name_and_value());
}
cfg_info.hidden_cfg.insert(simple);
}
}
}
@ -709,7 +762,7 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator<Item = &'a hir::Attribute>
fn check_changed_auto_active_status(
changed_auto_active_status: &mut Option<rustc_span::Span>,
attr: &ast::MetaItem,
attr_span: Span,
cfg_info: &mut CfgInfo,
tcx: TyCtxt<'_>,
new_value: bool,
@ -719,14 +772,14 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator<Item = &'a hir::Attribute>
tcx.sess
.dcx()
.struct_span_err(
vec![*first_change, attr.span],
vec![*first_change, attr_span],
"`auto_cfg` was disabled and enabled more than once on the same item",
)
.emit();
return true;
}
} else {
*changed_auto_active_status = Some(attr.span);
*changed_auto_active_status = Some(attr_span);
}
cfg_info.auto_cfg_active = new_value;
false
@ -737,28 +790,21 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator<Item = &'a hir::Attribute>
let mut doc_cfg = attrs
.clone()
.filter(|attr| attr.has_name(sym::doc))
.flat_map(|attr| attr.meta_item_list().unwrap_or_default())
.filter(|attr| attr.has_name(sym::cfg))
.filter_map(|attr| match attr {
Attribute::Parsed(AttributeKind::Doc(d)) if !d.cfg.is_empty() => Some(d),
_ => None,
})
.peekable();
// If the item uses `doc(cfg(...))`, then we ignore the other `cfg(...)` attributes.
if doc_cfg.peek().is_some() {
let sess = tcx.sess;
// We overwrite existing `cfg`.
if !cfg_info.parent_is_doc_cfg {
cfg_info.current_cfg = Cfg::True;
cfg_info.current_cfg = Cfg(CfgEntry::Bool(true, DUMMY_SP));
cfg_info.parent_is_doc_cfg = true;
}
for attr in doc_cfg {
if let Some(cfg_mi) =
attr.meta_item().and_then(|attr| rustc_expand::config::parse_cfg_old(attr, sess))
{
match Cfg::parse(cfg_mi) {
Ok(new_cfg) => cfg_info.current_cfg &= new_cfg,
Err(e) => {
sess.dcx().span_err(e.span, e.msg);
}
}
for new_cfg in attr.cfg.clone() {
cfg_info.current_cfg &= Cfg(new_cfg);
}
}
} else {
@ -769,71 +815,47 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator<Item = &'a hir::Attribute>
// We get all `doc(auto_cfg)`, `cfg` and `target_feature` attributes.
for attr in attrs {
if let Some(ident) = attr.ident()
&& ident.name == sym::doc
&& let Some(attrs) = attr.meta_item_list()
{
for attr in attrs.iter().filter(|attr| attr.has_name(sym::auto_cfg)) {
let MetaItemInner::MetaItem(attr) = attr else {
continue;
};
match &attr.kind {
MetaItemKind::Word => {
if check_changed_auto_active_status(
&mut changed_auto_active_status,
attr,
cfg_info,
tcx,
true,
) {
return None;
}
}
MetaItemKind::NameValue(lit) => {
if let LitKind::Bool(value) = lit.kind {
if check_changed_auto_active_status(
&mut changed_auto_active_status,
attr,
cfg_info,
tcx,
value,
) {
return None;
}
}
}
MetaItemKind::List(sub_attrs) => {
if check_changed_auto_active_status(
&mut changed_auto_active_status,
attr,
cfg_info,
tcx,
true,
) {
return None;
}
for sub_attr in sub_attrs.iter() {
if let Some(ident) = sub_attr.ident()
&& (ident.name == sym::show || ident.name == sym::hide)
{
handle_auto_cfg_hide_show(
tcx,
cfg_info,
&sub_attr,
ident.name == sym::show,
&mut new_show_attrs,
&mut new_hide_attrs,
);
}
}
}
if let Attribute::Parsed(AttributeKind::Doc(d)) = attr {
for (new_value, span) in &d.auto_cfg_change {
if check_changed_auto_active_status(
&mut changed_auto_active_status,
*span,
cfg_info,
tcx,
*new_value,
) {
return None;
}
}
if let Some((_, span)) = d.auto_cfg.first() {
if check_changed_auto_active_status(
&mut changed_auto_active_status,
*span,
cfg_info,
tcx,
true,
) {
return None;
}
for (value, _) in &d.auto_cfg {
handle_auto_cfg_hide_show(
tcx,
cfg_info,
value,
&mut new_show_attrs,
&mut new_hide_attrs,
);
}
}
} else if let hir::Attribute::Parsed(AttributeKind::TargetFeature { features, .. }) = attr {
// Treat `#[target_feature(enable = "feat")]` attributes as if they were
// `#[doc(cfg(target_feature = "feat"))]` attributes as well.
for (feature, _) in features {
cfg_info.current_cfg &= Cfg::Cfg(sym::target_feature, Some(*feature));
cfg_info.current_cfg &= Cfg(CfgEntry::NameValue {
name: sym::target_feature,
value: Some(*feature),
span: DUMMY_SP,
});
}
continue;
} else if !cfg_info.parent_is_doc_cfg
@ -851,7 +873,7 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator<Item = &'a hir::Attribute>
if !cfg_info.auto_cfg_active && !cfg_info.parent_is_doc_cfg {
None
} else if cfg_info.parent_is_doc_cfg {
if cfg_info.current_cfg == Cfg::True {
if matches!(cfg_info.current_cfg.0, CfgEntry::Bool(true, _)) {
None
} else {
Some(Arc::new(cfg_info.current_cfg.clone()))
@ -859,9 +881,9 @@ pub(crate) fn extract_cfg_from_attrs<'a, I: Iterator<Item = &'a hir::Attribute>
} else {
// If `doc(auto_cfg)` feature is enabled, we want to collect all `cfg` items, we remove the
// hidden ones afterward.
match cfg_info.current_cfg.strip_hidden(&cfg_info.hidden_cfg) {
None | Some(Cfg::True) => None,
Some(cfg) => Some(Arc::new(cfg)),
match strip_hidden(&cfg_info.current_cfg.0, &cfg_info.hidden_cfg) {
None | Some(CfgEntry::Bool(true, _)) => None,
Some(cfg) => Some(Arc::new(Cfg(cfg))),
}
}
}

View file

@ -1,23 +1,57 @@
use rustc_ast::ast::LitIntType;
use rustc_ast::{MetaItemInner, MetaItemLit, Path, Safety, StrStyle};
use rustc_data_structures::thin_vec::thin_vec;
use rustc_hir::attrs::CfgEntry;
use rustc_span::symbol::{Ident, kw};
use rustc_span::{DUMMY_SP, create_default_session_globals_then};
use super::*;
fn word_cfg(s: &str) -> Cfg {
Cfg::Cfg(Symbol::intern(s), None)
fn word_cfg(name: &str) -> Cfg {
Cfg(word_cfg_e(name))
}
fn word_cfg_e(name: &str) -> CfgEntry {
CfgEntry::NameValue { name: Symbol::intern(name), value: None, span: DUMMY_SP }
}
fn name_value_cfg(name: &str, value: &str) -> Cfg {
Cfg::Cfg(Symbol::intern(name), Some(Symbol::intern(value)))
Cfg(name_value_cfg_e(name, value))
}
fn name_value_cfg_e(name: &str, value: &str) -> CfgEntry {
CfgEntry::NameValue {
name: Symbol::intern(name),
value: Some(Symbol::intern(value)),
span: DUMMY_SP,
}
}
fn dummy_lit(symbol: Symbol, kind: LitKind) -> MetaItemInner {
MetaItemInner::Lit(MetaItemLit { symbol, suffix: None, kind, span: DUMMY_SP })
}
fn cfg_all(v: ThinVec<CfgEntry>) -> Cfg {
Cfg(cfg_all_e(v))
}
fn cfg_all_e(v: ThinVec<CfgEntry>) -> CfgEntry {
CfgEntry::All(v, DUMMY_SP)
}
fn cfg_any(v: ThinVec<CfgEntry>) -> Cfg {
Cfg(cfg_any_e(v))
}
fn cfg_any_e(v: ThinVec<CfgEntry>) -> CfgEntry {
CfgEntry::Any(v, DUMMY_SP)
}
fn cfg_not(v: CfgEntry) -> Cfg {
Cfg(CfgEntry::Not(Box::new(v), DUMMY_SP))
}
fn dummy_meta_item_word(name: &str) -> MetaItemInner {
MetaItemInner::MetaItem(MetaItem {
unsafety: Safety::Default,
@ -63,40 +97,48 @@ macro_rules! dummy_meta_item_list {
};
}
fn cfg_true() -> Cfg {
Cfg(CfgEntry::Bool(true, DUMMY_SP))
}
fn cfg_false() -> Cfg {
Cfg(CfgEntry::Bool(false, DUMMY_SP))
}
#[test]
fn test_cfg_not() {
create_default_session_globals_then(|| {
assert_eq!(!Cfg::False, Cfg::True);
assert_eq!(!Cfg::True, Cfg::False);
assert_eq!(!word_cfg("test"), Cfg::Not(Box::new(word_cfg("test"))));
assert_eq!(!cfg_false(), cfg_true());
assert_eq!(!cfg_true(), cfg_false());
assert_eq!(!word_cfg("test"), cfg_not(word_cfg_e("test")));
assert_eq!(
!Cfg::All(vec![word_cfg("a"), word_cfg("b")]),
Cfg::Not(Box::new(Cfg::All(vec![word_cfg("a"), word_cfg("b")])))
!cfg_all(thin_vec![word_cfg_e("a"), word_cfg_e("b")]),
cfg_not(cfg_all_e(thin_vec![word_cfg_e("a"), word_cfg_e("b")]))
);
assert_eq!(
!Cfg::Any(vec![word_cfg("a"), word_cfg("b")]),
Cfg::Not(Box::new(Cfg::Any(vec![word_cfg("a"), word_cfg("b")])))
!cfg_any(thin_vec![word_cfg_e("a"), word_cfg_e("b")]),
cfg_not(cfg_any_e(thin_vec![word_cfg_e("a"), word_cfg_e("b")]))
);
assert_eq!(!Cfg::Not(Box::new(word_cfg("test"))), word_cfg("test"));
assert_eq!(!cfg_not(word_cfg_e("test")), word_cfg("test"));
})
}
#[test]
fn test_cfg_and() {
create_default_session_globals_then(|| {
let mut x = Cfg::False;
x &= Cfg::True;
assert_eq!(x, Cfg::False);
let mut x = cfg_false();
x &= cfg_true();
assert_eq!(x, cfg_false());
x = word_cfg("test");
x &= Cfg::False;
assert_eq!(x, Cfg::False);
x &= cfg_false();
assert_eq!(x, cfg_false());
x = word_cfg("test2");
x &= Cfg::True;
x &= cfg_true();
assert_eq!(x, word_cfg("test2"));
x = Cfg::True;
x = cfg_true();
x &= word_cfg("test3");
assert_eq!(x, word_cfg("test3"));
@ -104,63 +146,69 @@ fn test_cfg_and() {
assert_eq!(x, word_cfg("test3"));
x &= word_cfg("test4");
assert_eq!(x, Cfg::All(vec![word_cfg("test3"), word_cfg("test4")]));
assert_eq!(x, cfg_all(thin_vec![word_cfg_e("test3"), word_cfg_e("test4")]));
x &= word_cfg("test4");
assert_eq!(x, Cfg::All(vec![word_cfg("test3"), word_cfg("test4")]));
assert_eq!(x, cfg_all(thin_vec![word_cfg_e("test3"), word_cfg_e("test4")]));
x &= word_cfg("test5");
assert_eq!(x, Cfg::All(vec![word_cfg("test3"), word_cfg("test4"), word_cfg("test5")]));
x &= Cfg::All(vec![word_cfg("test6"), word_cfg("test7")]);
assert_eq!(
x,
Cfg::All(vec![
word_cfg("test3"),
word_cfg("test4"),
word_cfg("test5"),
word_cfg("test6"),
word_cfg("test7"),
cfg_all(thin_vec![word_cfg_e("test3"), word_cfg_e("test4"), word_cfg_e("test5")])
);
x &= cfg_all(thin_vec![word_cfg_e("test6"), word_cfg_e("test7")]);
assert_eq!(
x,
cfg_all(thin_vec![
word_cfg_e("test3"),
word_cfg_e("test4"),
word_cfg_e("test5"),
word_cfg_e("test6"),
word_cfg_e("test7"),
])
);
x &= Cfg::All(vec![word_cfg("test6"), word_cfg("test7")]);
x &= cfg_all(thin_vec![word_cfg_e("test6"), word_cfg_e("test7")]);
assert_eq!(
x,
Cfg::All(vec![
word_cfg("test3"),
word_cfg("test4"),
word_cfg("test5"),
word_cfg("test6"),
word_cfg("test7"),
cfg_all(thin_vec![
word_cfg_e("test3"),
word_cfg_e("test4"),
word_cfg_e("test5"),
word_cfg_e("test6"),
word_cfg_e("test7"),
])
);
let mut y = Cfg::Any(vec![word_cfg("a"), word_cfg("b")]);
let mut y = cfg_any(thin_vec![word_cfg_e("a"), word_cfg_e("b")]);
y &= x;
assert_eq!(
y,
Cfg::All(vec![
word_cfg("test3"),
word_cfg("test4"),
word_cfg("test5"),
word_cfg("test6"),
word_cfg("test7"),
Cfg::Any(vec![word_cfg("a"), word_cfg("b")]),
cfg_all(thin_vec![
word_cfg_e("test3"),
word_cfg_e("test4"),
word_cfg_e("test5"),
word_cfg_e("test6"),
word_cfg_e("test7"),
cfg_any_e(thin_vec![word_cfg_e("a"), word_cfg_e("b")]),
])
);
let mut z = word_cfg("test8");
z &= Cfg::All(vec![word_cfg("test9"), word_cfg("test10")]);
assert_eq!(z, Cfg::All(vec![word_cfg("test9"), word_cfg("test10"), word_cfg("test8")]));
z &= cfg_all(thin_vec![word_cfg_e("test9"), word_cfg_e("test10")]);
assert_eq!(
z,
cfg_all(thin_vec![word_cfg_e("test9"), word_cfg_e("test10"), word_cfg_e("test8"),]),
);
let mut z = word_cfg("test11");
z &= Cfg::All(vec![word_cfg("test11"), word_cfg("test12")]);
assert_eq!(z, Cfg::All(vec![word_cfg("test11"), word_cfg("test12")]));
z &= cfg_all(thin_vec![word_cfg_e("test11"), word_cfg_e("test12")]);
assert_eq!(z, cfg_all(thin_vec![word_cfg_e("test11"), word_cfg_e("test12")]));
assert_eq!(
word_cfg("a") & word_cfg("b") & word_cfg("c"),
Cfg::All(vec![word_cfg("a"), word_cfg("b"), word_cfg("c")])
cfg_all(thin_vec![word_cfg_e("a"), word_cfg_e("b"), word_cfg_e("c")])
);
})
}
@ -168,19 +216,19 @@ fn test_cfg_and() {
#[test]
fn test_cfg_or() {
create_default_session_globals_then(|| {
let mut x = Cfg::True;
x |= Cfg::False;
assert_eq!(x, Cfg::True);
let mut x = cfg_true();
x |= cfg_false();
assert_eq!(x, cfg_true());
x = word_cfg("test");
x |= Cfg::True;
x |= cfg_true();
assert_eq!(x, word_cfg("test"));
x = word_cfg("test2");
x |= Cfg::False;
x |= cfg_false();
assert_eq!(x, word_cfg("test2"));
x = Cfg::False;
x = cfg_false();
x |= word_cfg("test3");
assert_eq!(x, word_cfg("test3"));
@ -188,63 +236,69 @@ fn test_cfg_or() {
assert_eq!(x, word_cfg("test3"));
x |= word_cfg("test4");
assert_eq!(x, Cfg::Any(vec![word_cfg("test3"), word_cfg("test4")]));
assert_eq!(x, cfg_any(thin_vec![word_cfg_e("test3"), word_cfg_e("test4")]));
x |= word_cfg("test4");
assert_eq!(x, Cfg::Any(vec![word_cfg("test3"), word_cfg("test4")]));
assert_eq!(x, cfg_any(thin_vec![word_cfg_e("test3"), word_cfg_e("test4")]));
x |= word_cfg("test5");
assert_eq!(x, Cfg::Any(vec![word_cfg("test3"), word_cfg("test4"), word_cfg("test5")]));
x |= Cfg::Any(vec![word_cfg("test6"), word_cfg("test7")]);
assert_eq!(
x,
Cfg::Any(vec![
word_cfg("test3"),
word_cfg("test4"),
word_cfg("test5"),
word_cfg("test6"),
word_cfg("test7"),
cfg_any(thin_vec![word_cfg_e("test3"), word_cfg_e("test4"), word_cfg_e("test5")])
);
x |= cfg_any(thin_vec![word_cfg_e("test6"), word_cfg_e("test7")]);
assert_eq!(
x,
cfg_any(thin_vec![
word_cfg_e("test3"),
word_cfg_e("test4"),
word_cfg_e("test5"),
word_cfg_e("test6"),
word_cfg_e("test7"),
])
);
x |= Cfg::Any(vec![word_cfg("test6"), word_cfg("test7")]);
x |= cfg_any(thin_vec![word_cfg_e("test6"), word_cfg_e("test7")]);
assert_eq!(
x,
Cfg::Any(vec![
word_cfg("test3"),
word_cfg("test4"),
word_cfg("test5"),
word_cfg("test6"),
word_cfg("test7"),
cfg_any(thin_vec![
word_cfg_e("test3"),
word_cfg_e("test4"),
word_cfg_e("test5"),
word_cfg_e("test6"),
word_cfg_e("test7"),
])
);
let mut y = Cfg::All(vec![word_cfg("a"), word_cfg("b")]);
let mut y = cfg_all(thin_vec![word_cfg_e("a"), word_cfg_e("b")]);
y |= x;
assert_eq!(
y,
Cfg::Any(vec![
word_cfg("test3"),
word_cfg("test4"),
word_cfg("test5"),
word_cfg("test6"),
word_cfg("test7"),
Cfg::All(vec![word_cfg("a"), word_cfg("b")]),
cfg_any(thin_vec![
word_cfg_e("test3"),
word_cfg_e("test4"),
word_cfg_e("test5"),
word_cfg_e("test6"),
word_cfg_e("test7"),
cfg_all_e(thin_vec![word_cfg_e("a"), word_cfg_e("b")]),
])
);
let mut z = word_cfg("test8");
z |= Cfg::Any(vec![word_cfg("test9"), word_cfg("test10")]);
assert_eq!(z, Cfg::Any(vec![word_cfg("test9"), word_cfg("test10"), word_cfg("test8")]));
z |= cfg_any(thin_vec![word_cfg_e("test9"), word_cfg_e("test10")]);
assert_eq!(
z,
cfg_any(thin_vec![word_cfg_e("test9"), word_cfg_e("test10"), word_cfg_e("test8")])
);
let mut z = word_cfg("test11");
z |= Cfg::Any(vec![word_cfg("test11"), word_cfg("test12")]);
assert_eq!(z, Cfg::Any(vec![word_cfg("test11"), word_cfg("test12")]));
z |= cfg_any(thin_vec![word_cfg_e("test11"), word_cfg_e("test12")]);
assert_eq!(z, cfg_any(thin_vec![word_cfg_e("test11"), word_cfg_e("test12")]));
assert_eq!(
word_cfg("a") | word_cfg("b") | word_cfg("c"),
Cfg::Any(vec![word_cfg("a"), word_cfg("b"), word_cfg("c")])
cfg_any(thin_vec![word_cfg_e("a"), word_cfg_e("b"), word_cfg_e("c")])
);
})
}
@ -254,11 +308,11 @@ fn test_parse_ok() {
create_default_session_globals_then(|| {
let r#true = Symbol::intern("true");
let mi = dummy_lit(r#true, LitKind::Bool(true));
assert_eq!(Cfg::parse(&mi), Ok(Cfg::True));
assert_eq!(Cfg::parse(&mi), Ok(cfg_true()));
let r#false = Symbol::intern("false");
let mi = dummy_lit(r#false, LitKind::Bool(false));
assert_eq!(Cfg::parse(&mi), Ok(Cfg::False));
assert_eq!(Cfg::parse(&mi), Ok(cfg_false()));
let mi = dummy_meta_item_word("all");
assert_eq!(Cfg::parse(&mi), Ok(word_cfg("all")));
@ -464,33 +518,36 @@ fn test_simplify_with() {
// This is a tiny subset of things that could be simplified, but it likely covers 90% of
// real world usecases well.
create_default_session_globals_then(|| {
let foo = word_cfg("foo");
let bar = word_cfg("bar");
let baz = word_cfg("baz");
let quux = word_cfg("quux");
let foo = word_cfg_e("foo");
let bar = word_cfg_e("bar");
let baz = word_cfg_e("baz");
let quux = word_cfg_e("quux");
let foobar = Cfg::All(vec![foo.clone(), bar.clone()]);
let barbaz = Cfg::All(vec![bar.clone(), baz.clone()]);
let foobarbaz = Cfg::All(vec![foo.clone(), bar.clone(), baz.clone()]);
let bazquux = Cfg::All(vec![baz.clone(), quux.clone()]);
let foobar = cfg_all(thin_vec![foo.clone(), bar.clone()]);
let barbaz = cfg_all(thin_vec![bar.clone(), baz.clone()]);
let foobarbaz = cfg_all(thin_vec![foo.clone(), bar.clone(), baz.clone()]);
let bazquux = cfg_all(thin_vec![baz.clone(), quux.clone()]);
// Unrelated cfgs don't affect each other
assert_eq!(foo.simplify_with(&bar).as_ref(), Some(&foo));
assert_eq!(
Cfg(foo.clone()).simplify_with(&Cfg(bar.clone())).as_ref(),
Some(&Cfg(foo.clone()))
);
assert_eq!(foobar.simplify_with(&bazquux).as_ref(), Some(&foobar));
// Identical cfgs are eliminated
assert_eq!(foo.simplify_with(&foo), None);
assert_eq!(Cfg(foo.clone()).simplify_with(&Cfg(foo.clone())), None);
assert_eq!(foobar.simplify_with(&foobar), None);
// Multiple cfgs eliminate a single assumed cfg
assert_eq!(foobar.simplify_with(&foo).as_ref(), Some(&bar));
assert_eq!(foobar.simplify_with(&bar).as_ref(), Some(&foo));
assert_eq!(foobar.simplify_with(&Cfg(foo.clone())).as_ref(), Some(&Cfg(bar.clone())));
assert_eq!(foobar.simplify_with(&Cfg(bar)).as_ref(), Some(&Cfg(foo.clone())));
// A single cfg is eliminated by multiple assumed cfg containing it
assert_eq!(foo.simplify_with(&foobar), None);
assert_eq!(Cfg(foo.clone()).simplify_with(&foobar), None);
// Multiple cfgs eliminate the matching subset of multiple assumed cfg
assert_eq!(foobar.simplify_with(&barbaz).as_ref(), Some(&foo));
assert_eq!(foobar.simplify_with(&barbaz).as_ref(), Some(&Cfg(foo)));
assert_eq!(foobar.simplify_with(&foobarbaz), None);
});
}

View file

@ -640,7 +640,7 @@ pub(crate) fn build_impl(
for_,
items: trait_items,
polarity,
kind: if utils::has_doc_flag(tcx, did, sym::fake_variadic) {
kind: if utils::has_doc_flag(tcx, did, |d| d.fake_variadic.is_some()) {
ImplKind::FakeVariadic
} else {
ImplKind::Normal

View file

@ -34,13 +34,11 @@ use std::borrow::Cow;
use std::collections::BTreeMap;
use std::mem;
use rustc_ast::token::{Token, TokenKind};
use rustc_ast::tokenstream::{TokenStream, TokenTree};
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet, IndexEntry};
use rustc_data_structures::thin_vec::ThinVec;
use rustc_errors::codes::*;
use rustc_errors::{FatalError, struct_span_code_err};
use rustc_hir::attrs::AttributeKind;
use rustc_hir::attrs::{AttributeKind, DocAttribute, DocInline};
use rustc_hir::def::{CtorKind, DefKind, MacroKinds, Res};
use rustc_hir::def_id::{DefId, DefIdMap, DefIdSet, LOCAL_CRATE, LocalDefId};
use rustc_hir::{LangItem, PredicateOrigin, find_attr};
@ -198,12 +196,12 @@ fn generate_item_with_correct_attrs(
// For glob re-exports the item may or may not exist to be re-exported (potentially the
// cfgs on the path up until the glob can be removed, and only cfgs on the globbed item
// itself matter), for non-inlined re-exports see #85043.
let import_is_inline =
hir_attr_lists(inline::load_attrs(cx, import_id.to_def_id()), sym::doc)
.get_word_attr(sym::inline)
.is_some()
|| (is_glob_import(cx.tcx, import_id)
&& (cx.document_hidden() || !cx.tcx.is_doc_hidden(def_id)));
let import_is_inline = find_attr!(
inline::load_attrs(cx, import_id.to_def_id()),
AttributeKind::Doc(d)
if d.inline.first().is_some_and(|(inline, _)| *inline == DocInline::Inline)
) || (is_glob_import(cx.tcx, import_id)
&& (cx.document_hidden() || !cx.tcx.is_doc_hidden(def_id)));
attrs.extend(get_all_import_attributes(cx, import_id, def_id, is_inline));
is_inline = is_inline || import_is_inline;
}
@ -2635,63 +2633,6 @@ fn get_all_import_attributes<'hir>(
attrs
}
fn filter_tokens_from_list(
args_tokens: &TokenStream,
should_retain: impl Fn(&TokenTree) -> bool,
) -> Vec<TokenTree> {
let mut tokens = Vec::with_capacity(args_tokens.len());
let mut skip_next_comma = false;
for token in args_tokens.iter() {
match token {
TokenTree::Token(Token { kind: TokenKind::Comma, .. }, _) if skip_next_comma => {
skip_next_comma = false;
}
token if should_retain(token) => {
skip_next_comma = false;
tokens.push(token.clone());
}
_ => {
skip_next_comma = true;
}
}
}
tokens
}
fn filter_doc_attr_ident(ident: Symbol, is_inline: bool) -> bool {
if is_inline {
ident == sym::hidden || ident == sym::inline || ident == sym::no_inline
} else {
ident == sym::cfg
}
}
/// Remove attributes from `normal` that should not be inherited by `use` re-export.
/// Before calling this function, make sure `normal` is a `#[doc]` attribute.
fn filter_doc_attr(args: &mut hir::AttrArgs, is_inline: bool) {
match args {
hir::AttrArgs::Delimited(args) => {
let tokens = filter_tokens_from_list(&args.tokens, |token| {
!matches!(
token,
TokenTree::Token(
Token {
kind: TokenKind::Ident(
ident,
_,
),
..
},
_,
) if filter_doc_attr_ident(*ident, is_inline),
)
});
args.tokens = TokenStream::new(tokens);
}
hir::AttrArgs::Empty | hir::AttrArgs::Eq { .. } => {}
}
}
/// When inlining items, we merge their attributes (and all the reexports attributes too) with the
/// final reexport. For example:
///
@ -2719,25 +2660,34 @@ fn add_without_unwanted_attributes<'hir>(
import_parent: Option<DefId>,
) {
for attr in new_attrs {
if attr.is_doc_comment().is_some() {
attrs.push((Cow::Borrowed(attr), import_parent));
continue;
}
let mut attr = attr.clone();
match attr {
hir::Attribute::Unparsed(ref mut normal) if let [ident] = &*normal.path.segments => {
let ident = ident.name;
if ident == sym::doc {
filter_doc_attr(&mut normal.args, is_inline);
attrs.push((Cow::Owned(attr), import_parent));
} else if is_inline || ident != sym::cfg_trace {
hir::Attribute::Parsed(AttributeKind::DocComment { .. }) => {
attrs.push((Cow::Borrowed(attr), import_parent));
}
hir::Attribute::Parsed(AttributeKind::Doc(box d)) => {
// Remove attributes from `normal` that should not be inherited by `use` re-export.
let DocAttribute { hidden, inline, cfg, .. } = d;
let mut attr = DocAttribute::default();
if is_inline {
attr.cfg = cfg.clone();
} else {
attr.inline = inline.clone();
attr.hidden = hidden.clone();
}
attrs.push((
Cow::Owned(hir::Attribute::Parsed(AttributeKind::Doc(Box::new(attr)))),
import_parent,
));
}
hir::Attribute::Unparsed(normal) if let [ident] = &*normal.path.segments => {
if is_inline || ident.name != sym::cfg_trace {
// If it's not a `cfg()` attribute, we keep it.
attrs.push((Cow::Owned(attr), import_parent));
attrs.push((Cow::Borrowed(attr), import_parent));
}
}
// FIXME: make sure to exclude `#[cfg_trace]` here when it is ported to the new parsers
hir::Attribute::Parsed(..) => {
attrs.push((Cow::Owned(attr), import_parent));
attrs.push((Cow::Borrowed(attr), import_parent));
}
_ => {}
}
@ -2939,7 +2889,7 @@ fn clean_impl<'tcx>(
} else {
ty::ImplPolarity::Positive
},
kind: if utils::has_doc_flag(tcx, def_id.to_def_id(), sym::fake_variadic) {
kind: if utils::has_doc_flag(tcx, def_id.to_def_id(), |d| d.fake_variadic.is_some()) {
ImplKind::FakeVariadic
} else {
ImplKind::Normal
@ -2968,11 +2918,10 @@ fn clean_extern_crate<'tcx>(
let ty_vis = cx.tcx.visibility(krate.owner_id);
let please_inline = ty_vis.is_public()
&& attrs.iter().any(|a| {
a.has_name(sym::doc)
&& match a.meta_item_list() {
Some(l) => ast::attr::list_contains_name(&l, sym::inline),
None => false,
}
matches!(
a,
hir::Attribute::Parsed(AttributeKind::Doc(d))
if d.inline.first().is_some_and(|(i, _)| *i == DocInline::Inline))
})
&& !cx.is_json_output();
@ -3035,7 +2984,11 @@ fn clean_use_statement_inner<'tcx>(
let visibility = cx.tcx.visibility(import.owner_id);
let attrs = cx.tcx.hir_attrs(import.hir_id());
let inline_attr = hir_attr_lists(attrs, sym::doc).get_word_attr(sym::inline);
let inline_attr = find_attr!(
attrs,
AttributeKind::Doc(d) if d.inline.first().is_some_and(|(i, _)| *i == DocInline::Inline) => d
)
.and_then(|d| d.inline.first());
let pub_underscore = visibility.is_public() && name == Some(kw::Underscore);
let current_mod = cx.tcx.parent_module_from_def_id(import.owner_id.def_id);
let import_def_id = import.owner_id.def_id;
@ -3053,10 +3006,10 @@ fn clean_use_statement_inner<'tcx>(
let is_visible_from_parent_mod =
visibility.is_accessible_from(parent_mod, cx.tcx) && !current_mod.is_top_level_module();
if pub_underscore && let Some(ref inline) = inline_attr {
if pub_underscore && let Some((_, inline_span)) = inline_attr {
struct_span_code_err!(
cx.tcx.dcx(),
inline.span(),
*inline_span,
E0780,
"anonymous imports cannot be inlined"
)
@ -3071,16 +3024,11 @@ fn clean_use_statement_inner<'tcx>(
let mut denied = cx.is_json_output()
|| !(visibility.is_public() || (cx.document_private() && is_visible_from_parent_mod))
|| pub_underscore
|| attrs.iter().any(|a| {
a.has_name(sym::doc)
&& match a.meta_item_list() {
Some(l) => {
ast::attr::list_contains_name(&l, sym::no_inline)
|| ast::attr::list_contains_name(&l, sym::hidden)
}
None => false,
}
});
|| attrs.iter().any(|a| matches!(
a,
hir::Attribute::Parsed(AttributeKind::Doc(d))
if d.hidden.is_some() || d.inline.first().is_some_and(|(i, _)| *i == DocInline::NoInline)
));
// Also check whether imports were asked to be inlined, in case we're trying to re-export a
// crate in Rust 2018+

View file

@ -9,11 +9,11 @@ use itertools::Either;
use rustc_abi::{ExternAbi, VariantIdx};
use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet};
use rustc_data_structures::thin_vec::ThinVec;
use rustc_hir::attrs::{AttributeKind, DeprecatedSince, Deprecation};
use rustc_hir::attrs::{AttributeKind, DeprecatedSince, Deprecation, DocAttribute};
use rustc_hir::def::{CtorKind, DefKind, Res};
use rustc_hir::def_id::{CrateNum, DefId, LOCAL_CRATE, LocalDefId};
use rustc_hir::lang_items::LangItem;
use rustc_hir::{BodyId, ConstStability, Mutability, Stability, StableSince, find_attr};
use rustc_hir::{Attribute, BodyId, ConstStability, Mutability, Stability, StableSince, find_attr};
use rustc_index::IndexVec;
use rustc_metadata::rendered_const;
use rustc_middle::span_bug;
@ -190,12 +190,13 @@ impl ExternalCrate {
// Failing that, see if there's an attribute specifying where to find this
// external crate
let did = self.crate_num.as_def_id();
tcx.get_attrs(did, sym::doc)
.flat_map(|attr| attr.meta_item_list().unwrap_or_default())
.filter(|a| a.has_name(sym::html_root_url))
.filter_map(|a| a.value_str())
tcx.get_all_attrs(did)
.iter()
.find_map(|a| match a {
Attribute::Parsed(AttributeKind::Doc(d)) => d.html_root_url.map(|(url, _)| url),
_ => None,
})
.map(to_remote)
.next()
.or_else(|| extern_url.map(to_remote)) // NOTE: only matters if `extern_url_takes_precedence` is false
.unwrap_or(Unknown) // Well, at least we tried.
}
@ -228,25 +229,27 @@ impl ExternalCrate {
}
pub(crate) fn keywords(&self, tcx: TyCtxt<'_>) -> impl Iterator<Item = (DefId, Symbol)> {
self.retrieve_keywords_or_documented_attributes(tcx, sym::keyword)
self.retrieve_keywords_or_documented_attributes(tcx, |d| d.keyword.map(|(v, _)| v))
}
pub(crate) fn documented_attributes(
&self,
tcx: TyCtxt<'_>,
) -> impl Iterator<Item = (DefId, Symbol)> {
self.retrieve_keywords_or_documented_attributes(tcx, sym::attribute)
self.retrieve_keywords_or_documented_attributes(tcx, |d| d.attribute.map(|(v, _)| v))
}
fn retrieve_keywords_or_documented_attributes(
fn retrieve_keywords_or_documented_attributes<F: Fn(&DocAttribute) -> Option<Symbol>>(
&self,
tcx: TyCtxt<'_>,
name: Symbol,
callback: F,
) -> impl Iterator<Item = (DefId, Symbol)> {
let as_target = move |did: DefId, tcx: TyCtxt<'_>| -> Option<(DefId, Symbol)> {
tcx.get_attrs(did, sym::doc)
.flat_map(|attr| attr.meta_item_list().unwrap_or_default())
.filter(|meta| meta.has_name(name))
.find_map(|meta| meta.value_str())
tcx.get_all_attrs(did)
.iter()
.find_map(|attr| match attr {
Attribute::Parsed(AttributeKind::Doc(d)) => callback(d),
_ => None,
})
.map(|value| (did, value))
};
self.mapped_root_modules(tcx, as_target)
@ -920,37 +923,6 @@ pub(crate) struct Module {
pub(crate) span: Span,
}
pub(crate) fn hir_attr_lists<'a, I: IntoIterator<Item = &'a hir::Attribute>>(
attrs: I,
name: Symbol,
) -> impl Iterator<Item = ast::MetaItemInner> + use<'a, I> {
attrs
.into_iter()
.filter(move |attr| attr.has_name(name))
.filter_map(ast::attr::AttributeExt::meta_item_list)
.flatten()
}
pub(crate) trait NestedAttributesExt {
/// Returns `true` if the attribute list contains a specific `word`
fn has_word(self, word: Symbol) -> bool
where
Self: Sized,
{
<Self as NestedAttributesExt>::get_word_attr(self, word).is_some()
}
/// Returns `Some(attr)` if the attribute list contains 'attr'
/// corresponding to a specific `word`
fn get_word_attr(self, word: Symbol) -> Option<ast::MetaItemInner>;
}
impl<I: Iterator<Item = ast::MetaItemInner>> NestedAttributesExt for I {
fn get_word_attr(mut self, word: Symbol) -> Option<ast::MetaItemInner> {
self.find(|attr| attr.is_word() && attr.has_name(word))
}
}
/// A link that has not yet been rendered.
///
/// This link will be turned into a rendered link by [`Item::links`].
@ -993,28 +965,14 @@ pub(crate) struct Attributes {
}
impl Attributes {
pub(crate) fn lists(&self, name: Symbol) -> impl Iterator<Item = ast::MetaItemInner> {
hir_attr_lists(&self.other_attrs[..], name)
}
pub(crate) fn has_doc_flag(&self, flag: Symbol) -> bool {
for attr in &self.other_attrs {
if !attr.has_name(sym::doc) {
continue;
}
if let Some(items) = attr.meta_item_list()
&& items.iter().filter_map(|i| i.meta_item()).any(|it| it.has_name(flag))
{
return true;
}
}
false
pub(crate) fn has_doc_flag<F: Fn(&DocAttribute) -> bool>(&self, callback: F) -> bool {
self.other_attrs
.iter()
.any(|a| matches!(a, Attribute::Parsed(AttributeKind::Doc(d)) if callback(d)))
}
pub(crate) fn is_doc_hidden(&self) -> bool {
self.has_doc_flag(sym::hidden)
find_attr!(&self.other_attrs, AttributeKind::Doc(d) if d.hidden.is_some())
}
pub(crate) fn from_hir(attrs: &[hir::Attribute]) -> Attributes {
@ -1061,19 +1019,11 @@ impl Attributes {
pub(crate) fn get_doc_aliases(&self) -> Box<[Symbol]> {
let mut aliases = FxIndexSet::default();
for attr in
hir_attr_lists(&self.other_attrs[..], sym::doc).filter(|a| a.has_name(sym::alias))
{
if let Some(values) = attr.meta_item_list() {
for l in values {
if let Some(lit) = l.lit()
&& let ast::LitKind::Str(s, _) = lit.kind
{
aliases.insert(s);
}
for attr in &self.other_attrs {
if let Attribute::Parsed(AttributeKind::Doc(d)) = attr {
for (alias, _) in &d.aliases {
aliases.insert(*alias);
}
} else if let Some(value) = attr.value_str() {
aliases.insert(value);
}
}
aliases.into_iter().collect::<Vec<_>>().into()
@ -2416,7 +2366,7 @@ mod size_asserts {
use super::*;
// tidy-alphabetical-start
static_assert_size!(Crate, 16); // frequently moved by-value
static_assert_size!(DocFragment, 32);
static_assert_size!(DocFragment, 48);
static_assert_size!(GenericArg, 32);
static_assert_size!(GenericArgs, 24);
static_assert_size!(GenericParamDef, 40);

View file

@ -1,4 +1,5 @@
use rustc_resolve::rustdoc::{DocFragmentKind, unindent_doc_fragments};
use rustc_ast::token::{CommentKind, DocFragmentKind};
use rustc_resolve::rustdoc::unindent_doc_fragments;
use rustc_span::create_default_session_globals_then;
use super::*;
@ -8,7 +9,7 @@ fn create_doc_fragment(s: &str) -> Vec<DocFragment> {
span: DUMMY_SP,
item_id: None,
doc: Symbol::intern(s),
kind: DocFragmentKind::SugaredDoc,
kind: DocFragmentKind::Sugared(CommentKind::Line),
indent: 0,
from_expansion: false,
}]

Some files were not shown because too many files have changed in this diff Show more