diff --git a/src/item.rs b/src/item.rs index eb32d4f70b..de83e28aee 100644 --- a/src/item.rs +++ b/src/item.rs @@ -952,7 +952,8 @@ pub(crate) mod parsing { let vis: Visibility = ahead.parse()?; let lookahead = ahead.lookahead1(); - let mut item = if lookahead.peek(Token![fn]) || peek_signature(&ahead) { + let allow_safe = false; + let mut item = if lookahead.peek(Token![fn]) || peek_signature(&ahead, allow_safe) { let vis: Visibility = input.parse()?; let sig: Signature = input.parse()?; if input.peek(Token![;]) { @@ -1493,11 +1494,14 @@ pub(crate) mod parsing { } } - fn peek_signature(input: ParseStream) -> bool { + fn peek_signature(input: ParseStream, allow_safe: bool) -> bool { let fork = input.fork(); fork.parse::>().is_ok() && fork.parse::>().is_ok() - && fork.parse::>().is_ok() + && ((allow_safe + && token::parsing::peek_keyword(fork.cursor(), "safe") + && token::parsing::keyword(&fork, "safe").is_ok()) + || fork.parse::>().is_ok()) && fork.parse::>().is_ok() && fork.peek(Token![fn]) } @@ -1505,22 +1509,37 @@ pub(crate) mod parsing { #[cfg_attr(docsrs, doc(cfg(feature = "parsing")))] impl Parse for Signature { fn parse(input: ParseStream) -> Result { - let constness: Option = input.parse()?; - let asyncness: Option = input.parse()?; - let unsafety: Option = input.parse()?; - let abi: Option = input.parse()?; - let fn_token: Token![fn] = input.parse()?; - let ident: Ident = input.parse()?; - let mut generics: Generics = input.parse()?; + let allow_safe = false; + parse_signature(input, allow_safe).map(Option::unwrap) + } + } - let content; - let paren_token = parenthesized!(content in input); - let (inputs, variadic) = parse_fn_args(&content)?; + fn parse_signature(input: ParseStream, allow_safe: bool) -> Result> { + let constness: Option = input.parse()?; + let asyncness: Option = input.parse()?; + let unsafety: Option = input.parse()?; + let safe = allow_safe + && unsafety.is_none() + && token::parsing::peek_keyword(input.cursor(), "safe"); + if safe { + token::parsing::keyword(input, "safe")?; + } + let abi: Option = input.parse()?; + let fn_token: Token![fn] = input.parse()?; + let ident: Ident = input.parse()?; + let mut generics: Generics = input.parse()?; - let output: ReturnType = input.parse()?; - generics.where_clause = input.parse()?; + let content; + let paren_token = parenthesized!(content in input); + let (inputs, variadic) = parse_fn_args(&content)?; - Ok(Signature { + let output: ReturnType = input.parse()?; + generics.where_clause = input.parse()?; + + Ok(if safe { + None + } else { + Some(Signature { constness, asyncness, unsafety, @@ -1533,7 +1552,7 @@ pub(crate) mod parsing { variadic, output, }) - } + }) } #[cfg_attr(docsrs, doc(cfg(feature = "parsing")))] @@ -1829,35 +1848,55 @@ pub(crate) mod parsing { let vis: Visibility = ahead.parse()?; let lookahead = ahead.lookahead1(); - let mut item = if lookahead.peek(Token![fn]) || peek_signature(&ahead) { + let allow_safe = true; + let mut item = if lookahead.peek(Token![fn]) || peek_signature(&ahead, allow_safe) { let vis: Visibility = input.parse()?; - let sig: Signature = input.parse()?; - if input.peek(token::Brace) { + let sig = parse_signature(input, allow_safe)?; + let has_safe = sig.is_none(); + let has_body = input.peek(token::Brace); + let semi_token: Option = if has_body { let content; braced!(content in input); content.call(Attribute::parse_inner)?; content.call(Block::parse_within)?; - + None + } else { + Some(input.parse()?) + }; + if has_safe || has_body { Ok(ForeignItem::Verbatim(verbatim::between(&begin, input))) } else { Ok(ForeignItem::Fn(ForeignItemFn { attrs: Vec::new(), vis, - sig, - semi_token: input.parse()?, + sig: sig.unwrap(), + semi_token: semi_token.unwrap(), })) } - } else if lookahead.peek(Token![static]) { + } else if lookahead.peek(Token![static]) + || ((ahead.peek(Token![unsafe]) + || token::parsing::peek_keyword(ahead.cursor(), "safe")) + && ahead.peek2(Token![static])) + { let vis = input.parse()?; + let unsafety: Option = input.parse()?; + let safe = + unsafety.is_none() && token::parsing::peek_keyword(input.cursor(), "safe"); + if safe { + token::parsing::keyword(input, "safe")?; + } let static_token = input.parse()?; let mutability = input.parse()?; let ident = input.parse()?; let colon_token = input.parse()?; let ty = input.parse()?; - if input.peek(Token![=]) { + let has_value = input.peek(Token![=]); + if has_value { input.parse::()?; input.parse::()?; - input.parse::()?; + } + let semi_token: Token![;] = input.parse()?; + if safe || has_value { Ok(ForeignItem::Verbatim(verbatim::between(&begin, input))) } else { Ok(ForeignItem::Static(ForeignItemStatic { @@ -1868,7 +1907,7 @@ pub(crate) mod parsing { ident, colon_token, ty, - semi_token: input.parse()?, + semi_token, })) } } else if lookahead.peek(Token![type]) { @@ -2300,7 +2339,8 @@ pub(crate) mod parsing { let ahead = input.fork(); let lookahead = ahead.lookahead1(); - let mut item = if lookahead.peek(Token![fn]) || peek_signature(&ahead) { + let allow_safe = false; + let mut item = if lookahead.peek(Token![fn]) || peek_signature(&ahead, allow_safe) { input.parse().map(TraitItem::Fn) } else if lookahead.peek(Token![const]) { let const_token: Token![const] = ahead.parse()?; @@ -2645,7 +2685,8 @@ pub(crate) mod parsing { None }; - let mut item = if lookahead.peek(Token![fn]) || peek_signature(&ahead) { + let allow_safe = false; + let mut item = if lookahead.peek(Token![fn]) || peek_signature(&ahead, allow_safe) { let allow_omitted_body = true; if let Some(item) = parse_impl_item_fn(input, allow_omitted_body)? { Ok(ImplItem::Fn(item)) diff --git a/tests/repo/mod.rs b/tests/repo/mod.rs index 9f8a418ab9..26da5b58ac 100644 --- a/tests/repo/mod.rs +++ b/tests/repo/mod.rs @@ -19,12 +19,6 @@ const REVISION: &str = "5069856495870486134dd2ca0b0e2516308c5c2a"; #[rustfmt::skip] static EXCLUDE_FILES: &[&str] = &[ - // TODO: `unsafe static`, `safe fn` - // https://github.com/dtolnay/syn/issues/1675 - "src/tools/rustfmt/tests/target/unsafe_extern_blocks.rs", - "tests/rustdoc/unsafe-extern-blocks.rs", - "tests/ui/rust-2024/unsafe-extern-blocks/safe-items.rs", - // TODO: non-lifetime binders: `where for<'a, T> &'a Struct: Trait` // https://github.com/dtolnay/syn/issues/1435 "src/tools/rustfmt/tests/source/issue_5721.rs", @@ -301,6 +295,7 @@ static EXCLUDE_FILES: &[&str] = &[ "src/tools/rustfmt/tests/target/configs/spaces_around_ranges/false.rs", "src/tools/rustfmt/tests/target/configs/spaces_around_ranges/true.rs", "src/tools/rustfmt/tests/target/type.rs", + "src/tools/rustfmt/tests/target/unsafe_extern_blocks.rs", "tests/run-make/translation/test.rs", "tests/ui/generics/issue-94432-garbage-ice.rs",