diff --git a/.clippy.toml b/.clippy.toml new file mode 100644 index 0000000000..3d30690f12 --- /dev/null +++ b/.clippy.toml @@ -0,0 +1 @@ +msrv = "1.31.0" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ebf7161687..071aee790d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -74,14 +74,27 @@ jobs: - run: cargo check --manifest-path examples/lazy-static/example/Cargo.toml - run: cargo check --manifest-path examples/trace-var/example/Cargo.toml + docs: + name: Docs + runs-on: ubuntu-latest + env: + RUSTDOCFLAGS: --cfg=doc_cfg -Dbroken_intra_doc_links + steps: + - uses: actions/checkout@v2 + - uses: dtolnay/rust-toolchain@nightly + - run: cargo doc --all-features + codegen: name: Codegen runs-on: ubuntu-latest + env: + CFG_RELEASE_CHANNEL: dev + CFG_RELEASE: 1.51.0 steps: - uses: actions/checkout@v2 - uses: dtolnay/rust-toolchain@master with: - toolchain: nightly-2020-05-15 + toolchain: nightly-2021-01-01 - run: cd codegen && cargo run - run: git diff --exit-code @@ -100,4 +113,4 @@ jobs: steps: - uses: actions/checkout@v2 - uses: dtolnay/rust-toolchain@clippy - - run: cargo clippy --all-features + - run: cargo clippy --all-features -- -Dclippy::all -Dclippy::pedantic diff --git a/Cargo.toml b/Cargo.toml index 32b27eeb8d..0737d18f1d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "syn" -version = "1.0.53" # don't forget to update html_root_url and syn.json +version = "1.0.62" # don't forget to update html_root_url and syn.json authors = ["David Tolnay "] license = "MIT OR Apache-2.0" description = "Parser for Rust source code" @@ -48,7 +48,7 @@ ref-cast = "1.0" regex = "1.0" reqwest = { version = "0.10", features = ["blocking"] } syn-test-suite = { version = "0", path = "tests/features" } -tar = "0.4" +tar = "0.4.16" termcolor = "1.0" walkdir = "2.1" diff --git a/build.rs b/build.rs index cf7681c3f9..25190f4c87 100644 --- a/build.rs +++ b/build.rs @@ -15,6 +15,10 @@ fn main() { println!("cargo:rustc-cfg=syn_omit_await_from_token_macro"); } + if compiler.minor < 39 { + println!("cargo:rustc-cfg=syn_no_const_vec_new"); + } + if !compiler.nightly { println!("cargo:rustc-cfg=syn_disable_nightly_tests"); } diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index 39fa27fa0f..47928f6e0b 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -22,9 +22,9 @@ thiserror = "1.0" toml = "0.5" [dependencies.rustfmt] -package = "rustfmt_lib" -git = "https://github.com/rust-lang-nursery/rustfmt" -rev = "99edc8826ee6d7a5d529e8749cdf4ac999dfd954" +package = "rustfmt-nightly" +git = "https://github.com/rust-lang/rustfmt" +rev = "367a874d04abfb2269ff1ea1974f06640546b7c5" [workspace] # Prefer that `cargo clean` in syn's directory does not require a rebuild of diff --git a/codegen/rust-toolchain b/codegen/rust-toolchain index 8737a7eb56..a2b82fb1f4 100644 --- a/codegen/rust-toolchain +++ b/codegen/rust-toolchain @@ -1 +1 @@ -nightly-2020-05-15 +nightly-2021-01-01 diff --git a/codegen/src/file.rs b/codegen/src/file.rs index 5521d75984..0eeb0c74c1 100644 --- a/codegen/src/file.rs +++ b/codegen/src/file.rs @@ -14,14 +14,21 @@ pub fn write>(path: P, content: TokenStream) -> Result<()> { writeln!(formatted)?; let mut config = rustfmt::Config::default(); - config.set().emit_mode(rustfmt::EmitMode::Stdout); - config.set().verbose(rustfmt::Verbosity::Quiet); config.set().format_macro_matchers(true); config.set().normalize_doc_attributes(true); - let mut session = rustfmt::Session::new(config, Some(&mut formatted)); - session.format(rustfmt::Input::Text(content.to_string()))?; - drop(session); + let format_report = rustfmt::format( + rustfmt::Input::Text(content.to_string()), + &config, + rustfmt::OperationSetting { + recursive: false, + verbosity: rustfmt::emitter::Verbosity::Normal, + }, + )?; + + for (_filename, format_result) in format_report.format_result() { + write!(formatted, "{}", format_result.formatted_text())?; + } if path.as_ref().is_file() && fs::read(&path)? == formatted { return Ok(()); diff --git a/codegen/src/parse.rs b/codegen/src/parse.rs index 1e0284bcf9..716ba0dc7c 100644 --- a/codegen/src/parse.rs +++ b/codegen/src/parse.rs @@ -17,7 +17,6 @@ const SYN_CRATE_ROOT: &str = "../src/lib.rs"; const TOKEN_SRC: &str = "../src/token.rs"; const IGNORED_MODS: &[&str] = &["fold", "visit", "visit_mut"]; const EXTRA_TYPES: &[&str] = &["Lifetime"]; -const NONEXHAUSTIVE: &str = "__Nonexhaustive"; // NOTE: BTreeMap is used here instead of HashMap to have deterministic output. type ItemLookup = BTreeMap; @@ -63,7 +62,7 @@ fn introspect_item(item: &AstItem, items: &ItemLookup, tokens: &TokenLookup) -> ident: item.ast.ident.to_string(), features, data: types::Data::Enum(introspect_enum(data, items, tokens)), - exhaustive: data.variants.iter().all(|v| v.ident != NONEXHAUSTIVE), + exhaustive: !data.variants.iter().any(|v| is_doc_hidden(&v.attrs)), }, Data::Struct(ref data) => types::Node { ident: item.ast.ident.to_string(), @@ -85,7 +84,7 @@ fn introspect_enum(item: &DataEnum, items: &ItemLookup, tokens: &TokenLookup) -> item.variants .iter() .filter_map(|variant| { - if variant.ident == NONEXHAUSTIVE { + if is_doc_hidden(&variant.attrs) { return None; } let fields = match &variant.fields { @@ -215,6 +214,20 @@ fn is_pub(vis: &Visibility) -> bool { } } +fn is_doc_hidden(attrs: &[Attribute]) -> bool { + for attr in attrs { + if attr.path.is_ident("doc") { + if parsing::parse_doc_hidden_attr + .parse2(attr.tokens.clone()) + .is_ok() + { + return true; + } + } + } + false +} + fn first_arg(params: &PathArguments) -> &syn::Type { let data = match *params { PathArguments::AngleBracketed(ref data) => data, @@ -252,7 +265,7 @@ mod parsing { use proc_macro2::{TokenStream, TokenTree}; use quote::quote; use std::collections::{BTreeMap, BTreeSet}; - use syn::parse::{ParseStream, Result}; + use syn::parse::{ParseStream, Parser, Result}; use syn::{ braced, bracketed, parenthesized, parse_quote, token, Attribute, Ident, LitStr, Path, Token, }; @@ -331,11 +344,12 @@ mod parsing { // A single variant of an ast_enum_of_structs! struct EosVariant { + attrs: Vec, name: Ident, member: Option, } fn eos_variant(input: ParseStream) -> Result { - input.call(Attribute::parse_outer)?; + let attrs = input.call(Attribute::parse_outer)?; let variant: Ident = input.parse()?; let member = if input.peek(token::Paren) { let content; @@ -347,6 +361,7 @@ mod parsing { }; input.parse::()?; Ok(EosVariant { + attrs, name: variant, member, }) @@ -371,10 +386,11 @@ mod parsing { let enum_item = { let variants = variants.iter().map(|v| { - let name = v.name.clone(); + let attrs = &v.attrs; + let name = &v.name; match v.member { - Some(ref member) => quote!(#name(#member)), - None => quote!(#name), + Some(ref member) => quote!(#(#attrs)* #name(#member)), + None => quote!(#(#attrs)* #name), } }); parse_quote! { @@ -390,6 +406,7 @@ mod parsing { } mod kw { + syn::custom_keyword!(hidden); syn::custom_keyword!(macro_rules); syn::custom_keyword!(Token); } @@ -475,6 +492,27 @@ mod parsing { Ok(types::Features { any: features }) } + + pub fn path_attr(attrs: &[Attribute]) -> Result> { + for attr in attrs { + if attr.path.is_ident("path") { + fn parser(input: ParseStream) -> Result { + input.parse::()?; + input.parse() + } + let filename = parser.parse2(attr.tokens.clone())?; + return Ok(Some(filename)); + } + } + Ok(None) + } + + pub fn parse_doc_hidden_attr(input: ParseStream) -> Result<()> { + let content; + parenthesized!(content in input); + content.parse::()?; + Ok(()) + } } fn clone_features(features: &[Attribute]) -> Vec { @@ -565,7 +603,11 @@ fn do_load_file>( // Look up the submodule file, and recursively parse it. // Only handles same-directory .rs file submodules for now. - let path = parent.join(&format!("{}.rs", item.ident)); + let filename = match parsing::path_attr(&item.attrs)? { + Some(filename) => filename.value(), + None => format!("{}.rs", item.ident), + }; + let path = parent.join(filename); load_file(path, &features, lookup)?; } Item::Macro(item) => { diff --git a/dev/main.rs b/dev/main.rs index eb675465f0..8ef674bcd8 100644 --- a/dev/main.rs +++ b/dev/main.rs @@ -1,4 +1,550 @@ syn_dev::r#mod! { // Write Rust code here and run `cargo check` to have Syn parse it. +//! Ergonomic, checked cast functions for primitive types +//! +//! This crate provides one checked cast function for each numeric primitive. +//! Use these functions to perform a cast from any other numeric primitive: +//! +//! ``` +//! extern crate cast; +//! +//! use cast::{u8, u16, Error}; +//! +//! # fn main() { +//! // Infallible operations, like integer promotion, are equivalent to a normal +//! // cast with `as` +//! assert_eq!(u16(0u8), 0u16); +//! +//! // Everything else will return a `Result` depending on the success of the +//! // operation +//! assert_eq!(u8(0u16), Ok(0u8)); +//! assert_eq!(u8(256u16), Err(Error::Overflow)); +//! assert_eq!(u8(-1i8), Err(Error::Underflow)); +//! assert_eq!(u8(1. / 0.), Err(Error::Infinite)); +//! assert_eq!(u8(0. / 0.), Err(Error::NaN)); +//! # } +//! ``` +//! +//! There are no namespace problems between these functions, the "primitive +//! modules" in `core`/`std` and the built-in primitive types, so all them can +//! be in the same scope: +//! +//! ``` +//! extern crate cast; +//! +//! use std::u8; +//! use cast::{u8, u16}; +//! +//! # fn main() { +//! // `u8` as a type +//! let x: u8 = 0; +//! // `u8` as a module +//! let y = u16(u8::MAX); +//! // `u8` as a function +//! let z = u8(y).unwrap(); +//! # } +//! ``` +//! +//! The checked cast functionality is also usable with type aliases via the +//! `cast` static method: +//! +//! ``` +//! extern crate cast; +//! +//! use std::os::raw::c_ulonglong; +//! // NOTE avoid shadowing `std::convert::From` - cf. rust-lang/rfcs#1311 +//! use cast::From as _0; +//! +//! # fn main() { +//! assert_eq!(c_ulonglong::cast(0u8), 0u64); +//! # } +//! ``` +//! +//! This crate also provides a `From` trait that can be used, for example, +//! to create a generic function that accepts any type that can be infallibly +//! casted to `u32`. +//! +//! ``` +//! extern crate cast; +//! +//! fn to_u32(x: T) -> u32 +//! // reads as: "where u32 can be casted from T with output u32" +//! where u32: cast::From, +//! { +//! cast::u32(x) +//! } +//! +//! # fn main() { +//! assert_eq!(to_u32(0u8), 0u32); +//! assert_eq!(to_u32(1u16), 1u32); +//! assert_eq!(to_u32(2u32), 2u32); +//! +//! // to_u32(-1i32); // Compile error +//! # } +//! ``` +//! +//! ## Minimal Supported Rust Version +//! +//! This crate is guaranteed to compile on stable Rust 1.13 and up. It *might* compile on older +//! versions but that may change in any new patch release. +//! +//! ## Building without `std` +//! +//! This crate can be used without Rust's `std` crate by declaring it as +//! follows in your `Cargo.toml`: +//! +//! ``` toml +//! cast = { version = "*", default-features = false } +//! ``` + +#![deny(missing_docs)] +#![deny(warnings)] +#![allow(const_err)] +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(all(feature = "x128", not(stable_i128)), feature(i128_type, i128))] + +#[cfg(feature = "std")] +extern crate core; + +#[cfg(test)] +#[macro_use] +extern crate quickcheck; + +use core::fmt; +#[cfg(feature = "std")] +use std::error; + +#[cfg(test)] +mod test; + +/// Cast errors +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Error { + /// Infinite value casted to a type that can only represent finite values + Infinite, + /// NaN value casted to a type that can't represent a NaN value + NaN, + /// Source value is greater than the maximum value that the destination type + /// can hold + Overflow, + /// Source value is smaller than the minimum value that the destination type + /// can hold + Underflow, +} + +impl Error { + /// A private helper function that implements `description`, because + /// `description` is only available when we have `std` enabled. + fn description_helper(&self) -> &str { + match *self { + Error::Infinite => "Cannot store infinite value in finite type", + Error::NaN => "Cannot store NaN in type which does not support it", + Error::Overflow => "Overflow during numeric conversion", + Error::Underflow => "Underflow during numeric conversion", + } + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.description_helper()) + } +} + +#[cfg(feature = "std")] +impl error::Error for Error { + fn description(&self) -> &str { + self.description_helper() + } +} + +/// The "cast from" operation +pub trait From { + /// The result of the cast operation: either `Self` or `Result` + type Output; + + /// Checked cast from `Src` to `Self` + fn cast(Src) -> Self::Output; +} + +macro_rules! fns { + ($($ty:ident),+) => { + $( + /// Checked cast function + #[inline] + pub fn $ty(x: T) -> <$ty as From>::Output + where $ty: From + { + <$ty as From>::cast(x) + } + )+ + } +} + +fns!(f32, f64, i8, i16, i32, i64, isize, u8, u16, u32, u64, usize); + +#[cfg(feature = "x128")] +fns!(i128, u128); + +/// `$dst` can hold any value of `$src` +macro_rules! promotion { + ($($src:ty => $($dst: ty),+);+;) => { + $( + $( + impl From<$src> for $dst { + type Output = $dst; + + #[inline] + fn cast(src: $src) -> $dst { + src as $dst + } + } + )+ + )+ + } +} + +/// `$dst` can hold any positive value of `$src` +macro_rules! half_promotion { + ($($src:ty => $($dst:ty),+);+;) => { + $( + $( + impl From<$src> for $dst { + type Output = Result<$dst, Error>; + + #[inline] + fn cast(src: $src) -> Self::Output { + if src < 0 { + Err(Error::Underflow) + } else { + Ok(src as $dst) + } + } + } + )+ + )+ + } +} + +/// From an unsigned `$src` to a smaller `$dst` +macro_rules! from_unsigned { + ($($src:ident => $($dst:ident),+);+;) => { + $( + $( + impl From<$src> for $dst { + type Output = Result<$dst, Error>; + + #[inline] + fn cast(src: $src) -> Self::Output { + use core::$dst; + + if src > $dst::MAX as $src { + Err(Error::Overflow) + } else { + Ok(src as $dst) + } + } + } + )+ + )+ + } +} + +/// From a signed `$src` to a smaller `$dst` +macro_rules! from_signed { + ($($src:ident => $($dst:ident),+);+;) => { + $( + $( + impl From<$src> for $dst { + type Output = Result<$dst, Error>; + + #[inline] + fn cast(src: $src) -> Self::Output { + use core::$dst; + + Err(if src < $dst::MIN as $src { + Error::Underflow + } else if src > $dst::MAX as $src { + Error::Overflow + } else { + return Ok(src as $dst); + }) + } + } + )+ + )+ + } +} + +/// From a float `$src` to an integer `$dst` +macro_rules! from_float { + ($($src:ident, $usrc:ident => $($dst:ident),+);+;) => { + $( + $( + impl From<$src> for $dst { + type Output = Result<$dst, Error>; + + #[inline] + fn cast(src: $src) -> Self::Output { + use core::{$dst, $src}; + + Err(if src != src { + Error::NaN + } else if src == $src::INFINITY || + src == $src::NEG_INFINITY { + Error::Infinite + } else if { + // we subtract 1 ULP (unit of least precision) here because some + // lossy conversions like `u64::MAX as f64` round *up* and we want + // to avoid this evaluating to false in that case + use core::mem::transmute; + let max = unsafe { + transmute::<_, $src>(transmute::<_, $usrc>($dst::MAX as $src) - 1) + }; + src > max + } { + Error::Overflow + } else if $dst::MIN == 0 { + // when casting to unsigned integer, negative values close to 0 but + // larger than 1.0 should be truncated to 0; this behavior matches + // casting from a float to a signed integer + if src <= -1.0 { + Error::Underflow + } else { + return Ok(src as $dst); + } + } else if src < $dst::MIN as $src { + Error::Underflow + } else { + return Ok(src as $dst); + }) + } + } + )+ + )+ + } +} + +/// From a float `$src` to an integer `$dst`, where $dst is large enough to contain +/// all values of `$src`. We can't ever overflow here +#[cfg(feature = "x128")] +macro_rules! from_float_dst { + ($($src:ident => $($dst:ident),+);+;) => { + $( + $( + impl From<$src> for $dst { + type Output = Result<$dst, Error>; + + #[inline] + #[allow(unused_comparisons)] + fn cast(src: $src) -> Self::Output { + use core::{$dst, $src}; + + Err(if src != src { + Error::NaN + } else if src == $src::INFINITY || + src == $src::NEG_INFINITY { + Error::Infinite + } else if ($dst::MIN == 0) && src <= -1.0 { + Error::Underflow + } else { + return Ok(src as $dst); + }) + } + } + )+ + )+ + } +} + +// PLAY TETRIS! ;-) + +#[cfg(target_pointer_width = "32")] +mod _32 { + use {Error, From}; + + // Signed + promotion! { + i8 => f32, f64, i8, i16, i32, isize, i64; + i16 => f32, f64, i16, i32, isize, i64; + i32 => f32, f64, i32, isize, i64; + isize => f32, f64, i32, isize, i64; + i64 => f32, f64, i64; + } + + half_promotion! { + i8 => u8, u16, u32, usize, u64; + i16 => u16, u32, usize, u64; + i32 => u32, usize, u64; + isize => u32, usize, u64; + i64 => u64; + } + + from_signed! { + + i16 => i8, u8; + i32 => i8, i16, u8, u16; + isize => i8, i16, u8, u16; + i64 => i8, i16, i32, isize, u8, u16, u32, usize; + } + + // Unsigned + promotion! { + u8 => f32, f64, i16, i32, isize, i64, u8, u16, u32, usize, u64; + u16 => f32, f64, i32, isize, i64, u16, u32, usize, u64; + u32 => f32, f64, i64, u32, usize, u64; + usize => f32, f64, i64, u32, usize, u64; + u64 => f32, f64, u64; + } + + from_unsigned! { + u8 => i8; + u16 => i8, i16, u8; + u32 => i8, i16, i32, isize, u8, u16; + usize => i8, i16, i32, isize, u8, u16; + u64 => i8, i16, i32, isize, i64, u8, u16, u32, usize; + } + + // Float + promotion! { + f32 => f32, f64; + f64 => f64; + } + + from_float! { + f32, u32 => i8, i16, i32, isize, i64, u8, u16, u32, usize, u64; + f64, u64 => i8, i16, i32, isize, i64, u8, u16, u32, usize, u64; + } +} + +#[cfg(target_pointer_width = "64")] +mod _64 { + use {Error, From}; + + // Signed + promotion! { + i8 => f32, f64, i8, i16, i32, i64, isize; + i16 => f32, f64, i16, i32, i64, isize; + i32 => f32, f64, i32, i64, isize; + i64 => f32, f64, i64, isize; + isize => f32, f64, i64, isize; + } + + half_promotion! { + i8 => u8, u16, u32, u64, usize; + i16 => u16, u32, u64, usize; + i32 => u32, u64, usize; + i64 => u64, usize; + isize => u64, usize; + } + + from_signed! { + + i16 => i8, u8; + i32 => i8, i16, u8, u16; + i64 => i8, i16, i32, u8, u16, u32; + isize => i8, i16, i32, u8, u16, u32; + } + + // Unsigned + promotion! { + u8 => f32, f64, i16, i32, i64, isize, u8, u16, u32, u64, usize; + u16 => f32, f64, i32, i64, isize, u16, u32, u64, usize; + u32 => f32, f64, i64, isize, u32, u64, usize; + u64 => f32, f64, u64, usize; + usize => f32, f64, u64, usize; + } + + from_unsigned! { + u8 => i8; + u16 => i8, i16, u8; + u32 => i8, i16, i32, u8, u16; + u64 => i8, i16, i32, i64, isize, u8, u16, u32; + usize => i8, i16, i32, i64, isize, u8, u16, u32; + } + + // Float + promotion! { + f32 => f32, f64; + f64 => f64; + } + + from_float! { + f32, u32 => i8, i16, i32, i64, isize, u8, u16, u32, u64, usize; + f64, u64 => i8, i16, i32, i64, isize, u8, u16, u32, u64, usize; + } +} + +#[cfg(feature = "x128")] +mod _x128 { + use {Error, From}; + + // Signed + promotion! { + i8 => i128; + i16 => i128; + i32 => i128; + i64 => i128; + isize => i128; + i128 => f32, f64, i128; + } + + half_promotion! { + i8 => u128; + i16 => u128; + i32 => u128; + i64 => u128; + isize => u128; + i128 => u128; + } + + from_signed! { + i128 => i8, i16, i32, i64, isize, u8, u16, u32, u64, usize; + } + + // Unsigned + promotion! { + u8 => i128, u128; + u16 => i128, u128; + u32 => i128, u128; + u64 => i128, u128; + usize => i128, u128; + u128 => f64, u128; + } + + from_unsigned! { + u128 => f32, i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, usize; + } + + // Float + from_float! { + f32, u32 => i128; + f64, u64 => i128, u128; + } + + from_float_dst! { + f32 => u128; + } +} + +// The missing piece +impl From for f32 { + type Output = Result; + + #[inline] + fn cast(src: f64) -> Self::Output { + use core::{f32, f64}; + + if src != src || src == f64::INFINITY || src == f64::NEG_INFINITY { + Ok(src as f32) + } else if src < f32::MIN as f64 { + Err(Error::Underflow) + } else if src > f32::MAX as f64 { + Err(Error::Overflow) + } else { + Ok(src as f32) + } + } +} } diff --git a/src/attr.rs b/src/attr.rs index 2db1364bb7..794a3104f7 100644 --- a/src/attr.rs +++ b/src/attr.rs @@ -357,10 +357,7 @@ ast_enum_of_structs! { /// /// This type is a [syntax tree enum]. /// - /// [syntax tree enum]: enum.Expr.html#syntax-tree-enums - // - // TODO: change syntax-tree-enum link to an intra rustdoc link, currently - // blocked on https://github.com/rust-lang/rust/issues/62833 + /// [syntax tree enum]: Expr#syntax-tree-enums #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))] pub enum Meta { Path(Path), @@ -533,7 +530,7 @@ pub mod parsing { #[cfg(feature = "full")] impl private { - pub fn attrs(outer: Vec, inner: Vec) -> Vec { + pub(crate) fn attrs(outer: Vec, inner: Vec) -> Vec { let mut attrs = outer; attrs.extend(inner); attrs diff --git a/src/custom_keyword.rs b/src/custom_keyword.rs index eb9726ede6..a21842efa9 100644 --- a/src/custom_keyword.rs +++ b/src/custom_keyword.rs @@ -91,23 +91,23 @@ macro_rules! custom_keyword { ($ident:ident) => { #[allow(non_camel_case_types)] pub struct $ident { - pub span: $crate::export::Span, + pub span: $crate::__private::Span, } #[doc(hidden)] #[allow(dead_code, non_snake_case)] - pub fn $ident<__S: $crate::export::IntoSpans<[$crate::export::Span; 1]>>( + pub fn $ident<__S: $crate::__private::IntoSpans<[$crate::__private::Span; 1]>>( span: __S, ) -> $ident { $ident { - span: $crate::export::IntoSpans::into_spans(span)[0], + span: $crate::__private::IntoSpans::into_spans(span)[0], } } - impl $crate::export::Default for $ident { + impl $crate::__private::Default for $ident { fn default() -> Self { $ident { - span: $crate::export::Span::call_site(), + span: $crate::__private::Span::call_site(), } } } @@ -127,7 +127,7 @@ macro_rules! impl_parse_for_custom_keyword { ($ident:ident) => { // For peek. impl $crate::token::CustomToken for $ident { - fn peek(cursor: $crate::buffer::Cursor) -> $crate::export::bool { + fn peek(cursor: $crate::buffer::Cursor) -> $crate::__private::bool { if let Some((ident, _rest)) = cursor.ident() { ident == stringify!($ident) } else { @@ -135,7 +135,7 @@ macro_rules! impl_parse_for_custom_keyword { } } - fn display() -> &'static $crate::export::str { + fn display() -> &'static $crate::__private::str { concat!("`", stringify!($ident), "`") } } @@ -143,12 +143,12 @@ macro_rules! impl_parse_for_custom_keyword { impl $crate::parse::Parse for $ident { fn parse(input: $crate::parse::ParseStream) -> $crate::parse::Result<$ident> { input.step(|cursor| { - if let $crate::export::Some((ident, rest)) = cursor.ident() { + if let $crate::__private::Some((ident, rest)) = cursor.ident() { if ident == stringify!($ident) { - return $crate::export::Ok(($ident { span: ident.span() }, rest)); + return $crate::__private::Ok(($ident { span: ident.span() }, rest)); } } - $crate::export::Err(cursor.error(concat!( + $crate::__private::Err(cursor.error(concat!( "expected `", stringify!($ident), "`" @@ -173,10 +173,10 @@ macro_rules! impl_parse_for_custom_keyword { #[macro_export] macro_rules! impl_to_tokens_for_custom_keyword { ($ident:ident) => { - impl $crate::export::ToTokens for $ident { - fn to_tokens(&self, tokens: &mut $crate::export::TokenStream2) { + impl $crate::__private::ToTokens for $ident { + fn to_tokens(&self, tokens: &mut $crate::__private::TokenStream2) { let ident = $crate::Ident::new(stringify!($ident), self.span); - $crate::export::TokenStreamExt::append(tokens, ident); + $crate::__private::TokenStreamExt::append(tokens, ident); } } }; @@ -196,9 +196,9 @@ macro_rules! impl_to_tokens_for_custom_keyword { #[macro_export] macro_rules! impl_clone_for_custom_keyword { ($ident:ident) => { - impl $crate::export::Copy for $ident {} + impl $crate::__private::Copy for $ident {} - impl $crate::export::Clone for $ident { + impl $crate::__private::Clone for $ident { fn clone(&self) -> Self { *self } @@ -220,25 +220,25 @@ macro_rules! impl_clone_for_custom_keyword { #[macro_export] macro_rules! impl_extra_traits_for_custom_keyword { ($ident:ident) => { - impl $crate::export::Debug for $ident { - fn fmt(&self, f: &mut $crate::export::Formatter) -> $crate::export::fmt::Result { - $crate::export::Formatter::write_str( + impl $crate::__private::Debug for $ident { + fn fmt(&self, f: &mut $crate::__private::Formatter) -> $crate::__private::fmt::Result { + $crate::__private::Formatter::write_str( f, concat!("Keyword [", stringify!($ident), "]"), ) } } - impl $crate::export::Eq for $ident {} + impl $crate::__private::Eq for $ident {} - impl $crate::export::PartialEq for $ident { - fn eq(&self, _other: &Self) -> $crate::export::bool { + impl $crate::__private::PartialEq for $ident { + fn eq(&self, _other: &Self) -> $crate::__private::bool { true } } - impl $crate::export::Hash for $ident { - fn hash<__H: $crate::export::Hasher>(&self, _state: &mut __H) {} + impl $crate::__private::Hash for $ident { + fn hash<__H: $crate::__private::Hasher>(&self, _state: &mut __H) {} } }; } diff --git a/src/custom_punctuation.rs b/src/custom_punctuation.rs index 2156ec2c64..128185da01 100644 --- a/src/custom_punctuation.rs +++ b/src/custom_punctuation.rs @@ -83,18 +83,18 @@ macro_rules! custom_punctuation { #[doc(hidden)] #[allow(dead_code, non_snake_case)] - pub fn $ident<__S: $crate::export::IntoSpans<$crate::custom_punctuation_repr!($($tt)+)>>( + pub fn $ident<__S: $crate::__private::IntoSpans<$crate::custom_punctuation_repr!($($tt)+)>>( spans: __S, ) -> $ident { let _validate_len = 0 $(+ $crate::custom_punctuation_len!(strict, $tt))*; $ident { - spans: $crate::export::IntoSpans::into_spans(spans) + spans: $crate::__private::IntoSpans::into_spans(spans) } } - impl $crate::export::Default for $ident { + impl $crate::__private::Default for $ident { fn default() -> Self { - $ident($crate::export::Span::call_site()) + $ident($crate::__private::Span::call_site()) } } @@ -116,7 +116,7 @@ macro_rules! impl_parse_for_custom_punctuation { $crate::token::parsing::peek_punct(cursor, $crate::stringify_punct!($($tt)+)) } - fn display() -> &'static $crate::export::str { + fn display() -> &'static $crate::__private::str { concat!("`", $crate::stringify_punct!($($tt)+), "`") } } @@ -145,8 +145,8 @@ macro_rules! impl_parse_for_custom_punctuation { #[macro_export] macro_rules! impl_to_tokens_for_custom_punctuation { ($ident:ident, $($tt:tt)+) => { - impl $crate::export::ToTokens for $ident { - fn to_tokens(&self, tokens: &mut $crate::export::TokenStream2) { + impl $crate::__private::ToTokens for $ident { + fn to_tokens(&self, tokens: &mut $crate::__private::TokenStream2) { $crate::token::printing::punct($crate::stringify_punct!($($tt)+), &self.spans, tokens) } } @@ -167,9 +167,9 @@ macro_rules! impl_to_tokens_for_custom_punctuation { #[macro_export] macro_rules! impl_clone_for_custom_punctuation { ($ident:ident, $($tt:tt)+) => { - impl $crate::export::Copy for $ident {} + impl $crate::__private::Copy for $ident {} - impl $crate::export::Clone for $ident { + impl $crate::__private::Clone for $ident { fn clone(&self) -> Self { *self } @@ -191,22 +191,22 @@ macro_rules! impl_clone_for_custom_punctuation { #[macro_export] macro_rules! impl_extra_traits_for_custom_punctuation { ($ident:ident, $($tt:tt)+) => { - impl $crate::export::Debug for $ident { - fn fmt(&self, f: &mut $crate::export::Formatter) -> $crate::export::fmt::Result { - $crate::export::Formatter::write_str(f, stringify!($ident)) + impl $crate::__private::Debug for $ident { + fn fmt(&self, f: &mut $crate::__private::Formatter) -> $crate::__private::fmt::Result { + $crate::__private::Formatter::write_str(f, stringify!($ident)) } } - impl $crate::export::Eq for $ident {} + impl $crate::__private::Eq for $ident {} - impl $crate::export::PartialEq for $ident { - fn eq(&self, _other: &Self) -> $crate::export::bool { + impl $crate::__private::PartialEq for $ident { + fn eq(&self, _other: &Self) -> $crate::__private::bool { true } } - impl $crate::export::Hash for $ident { - fn hash<__H: $crate::export::Hasher>(&self, _state: &mut __H) {} + impl $crate::__private::Hash for $ident { + fn hash<__H: $crate::__private::Hasher>(&self, _state: &mut __H) {} } }; } @@ -224,7 +224,7 @@ macro_rules! impl_extra_traits_for_custom_punctuation { #[macro_export] macro_rules! custom_punctuation_repr { ($($tt:tt)+) => { - [$crate::export::Span; 0 $(+ $crate::custom_punctuation_len!(lenient, $tt))+] + [$crate::__private::Span; 0 $(+ $crate::custom_punctuation_len!(lenient, $tt))+] }; } diff --git a/src/data.rs b/src/data.rs index 93d8119cc0..731f5a0a7e 100644 --- a/src/data.rs +++ b/src/data.rs @@ -32,10 +32,7 @@ ast_enum_of_structs! { /// /// This type is a [syntax tree enum]. /// - /// [syntax tree enum]: enum.Expr.html#syntax-tree-enums - // - // TODO: change syntax-tree-enum link to an intra rustdoc link, currently - // blocked on https://github.com/rust-lang/rust/issues/62833 + /// [syntax tree enum]: Expr#syntax-tree-enums #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))] pub enum Fields { /// Named fields of a struct or struct variant such as `Point { x: f64, @@ -184,10 +181,7 @@ ast_enum_of_structs! { /// /// This type is a [syntax tree enum]. /// - /// [syntax tree enum]: enum.Expr.html#syntax-tree-enums - // - // TODO: change syntax-tree-enum link to an intra rustdoc link, currently - // blocked on https://github.com/rust-lang/rust/issues/62833 + /// [syntax tree enum]: Expr#syntax-tree-enums #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))] pub enum Visibility { /// A public visibility level: `pub`. diff --git a/src/derive.rs b/src/derive.rs index 835fbda630..af9bb91b7a 100644 --- a/src/derive.rs +++ b/src/derive.rs @@ -33,10 +33,7 @@ ast_enum_of_structs! { /// /// This type is a [syntax tree enum]. /// - /// [syntax tree enum]: enum.Expr.html#syntax-tree-enums - // - // TODO: change syntax-tree-enum link to an intra rustdoc link, currently - // blocked on https://github.com/rust-lang/rust/issues/62833 + /// [syntax tree enum]: Expr#syntax-tree-enums #[cfg_attr(doc_cfg, doc(cfg(feature = "derive")))] pub enum Data { /// A struct input to a `proc_macro_derive` macro. diff --git a/src/error.rs b/src/error.rs index cc74c74459..6051f3b686 100644 --- a/src/error.rs +++ b/src/error.rs @@ -23,7 +23,7 @@ pub type Result = std::result::Result; /// [`compile_error!`] in the generated code. This produces a better diagnostic /// message than simply panicking the macro. /// -/// [`compile_error!`]: https://doc.rust-lang.org/std/macro.compile_error.html +/// [`compile_error!`]: std::compile_error! /// /// When parsing macro input, the [`parse_macro_input!`] macro handles the /// conversion to `compile_error!` automatically. @@ -189,7 +189,7 @@ impl Error { /// The [`parse_macro_input!`] macro provides a convenient way to invoke /// this method correctly in a procedural macro. /// - /// [`compile_error!`]: https://doc.rust-lang.org/std/macro.compile_error.html + /// [`compile_error!`]: std::compile_error! pub fn to_compile_error(&self) -> TokenStream { self.messages .iter() @@ -197,6 +197,42 @@ impl Error { .collect() } + /// Render the error as an invocation of [`compile_error!`]. + /// + /// [`compile_error!`]: std::compile_error! + /// + /// # Example + /// + /// ``` + /// # extern crate proc_macro; + /// # + /// use proc_macro::TokenStream; + /// use syn::{parse_macro_input, DeriveInput, Error}; + /// + /// # const _: &str = stringify! { + /// #[proc_macro_derive(MyTrait)] + /// # }; + /// pub fn derive_my_trait(input: TokenStream) -> TokenStream { + /// let input = parse_macro_input!(input as DeriveInput); + /// my_trait::expand(input) + /// .unwrap_or_else(Error::into_compile_error) + /// .into() + /// } + /// + /// mod my_trait { + /// use proc_macro2::TokenStream; + /// use syn::{DeriveInput, Result}; + /// + /// pub(crate) fn expand(input: DeriveInput) -> Result { + /// /* ... */ + /// # unimplemented!() + /// } + /// } + /// ``` + pub fn into_compile_error(self) -> TokenStream { + self.to_compile_error() + } + /// Add another error message to self such that when `to_compile_error()` is /// called, both errors will be emitted together. pub fn combine(&mut self, another: Error) { diff --git a/src/export.rs b/src/export.rs index 37dc467a7f..601a214b1e 100644 --- a/src/export.rs +++ b/src/export.rs @@ -33,3 +33,5 @@ mod help { pub type Bool = bool; pub type Str = str; } + +pub struct private(pub(crate) ()); diff --git a/src/expr.rs b/src/expr.rs index 8417475cab..510592de26 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -224,8 +224,31 @@ ast_enum_of_structs! { /// A yield expression: `yield expr`. Yield(ExprYield), + // The following is the only supported idiom for exhaustive matching of + // this enum. + // + // match expr { + // Expr::Array(e) => {...} + // Expr::Assign(e) => {...} + // ... + // Expr::Yield(e) => {...} + // + // #[cfg(test)] + // Expr::__TestExhaustive(_) => unimplemented!(), + // #[cfg(not(test))] + // _ => { /* some sane fallback */ } + // } + // + // This way we fail your tests but don't break your library when adding + // a variant. You will be notified by a test failure when a variant is + // added, so that you can add code to handle it, but your library will + // continue to compile and work for downstream users in the interim. + // + // Once `deny(reachable)` is available in rustc, Expr will be + // reimplemented as a non_exhaustive enum. + // https://github.com/rust-lang/rust/issues/44109#issuecomment-521781237 #[doc(hidden)] - __Nonexhaustive, + __TestExhaustive(crate::private), } } @@ -804,7 +827,11 @@ impl Expr { | Expr::TryBlock(ExprTryBlock { attrs, .. }) | Expr::Yield(ExprYield { attrs, .. }) => mem::replace(attrs, new), Expr::Verbatim(_) => Vec::new(), - Expr::__Nonexhaustive => unreachable!(), + + #[cfg(test)] + Expr::__TestExhaustive(_) => unimplemented!(), + #[cfg(not(test))] + _ => unreachable!(), } } } @@ -2303,7 +2330,11 @@ pub(crate) mod parsing { Pat::Type(_) => unreachable!(), Pat::Verbatim(_) => {} Pat::Wild(pat) => pat.attrs = attrs, - Pat::__Nonexhaustive => unreachable!(), + + #[cfg(test)] + Pat::__TestExhaustive(_) => unimplemented!(), + #[cfg(not(test))] + _ => unreachable!(), } Ok(pat) } @@ -2654,7 +2685,7 @@ pub(crate) mod parsing { } for part in float_repr.split('.') { let index = crate::parse_str(part).map_err(|err| Error::new(float.span(), err))?; - let base = mem::replace(e, Expr::__Nonexhaustive); + let base = mem::replace(e, Expr::__TestExhaustive(crate::private(()))); *e = Expr::Field(ExprField { attrs: Vec::new(), base: Box::new(base), diff --git a/src/file.rs b/src/file.rs index 6216016145..280484f980 100644 --- a/src/file.rs +++ b/src/file.rs @@ -51,21 +51,29 @@ ast_struct! { /// shebang: None, /// attrs: [], /// items: [ - /// ExternCrate( - /// ItemExternCrate { + /// Use( + /// ItemUse { /// attrs: [], /// vis: Inherited, - /// extern_token: Extern, - /// crate_token: Crate, - /// ident: Ident { - /// term: Term( - /// "syn" - /// ), - /// span: Span - /// }, - /// rename: None, - /// semi_token: Semi - /// } + /// use_token: Use, + /// leading_colon: None, + /// tree: Path( + /// UsePath { + /// ident: Ident( + /// std, + /// ), + /// colon2_token: Colon2, + /// tree: Name( + /// UseName { + /// ident: Ident( + /// env, + /// ), + /// }, + /// ), + /// }, + /// ), + /// semi_token: Semi, + /// }, /// ), /// ... /// ``` diff --git a/src/generics.rs b/src/generics.rs index 64228f81b1..a78af22ec6 100644 --- a/src/generics.rs +++ b/src/generics.rs @@ -31,7 +31,7 @@ ast_enum_of_structs! { /// /// This type is a [syntax tree enum]. /// - /// [syntax tree enum]: enum.Expr.html#syntax-tree-enums + /// [syntax tree enum]: Expr#syntax-tree-enums #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))] pub enum GenericParam { /// A generic type parameter: `T: Into`. @@ -538,7 +538,7 @@ ast_enum_of_structs! { /// /// This type is a [syntax tree enum]. /// - /// [syntax tree enum]: enum.Expr.html#syntax-tree-enums + /// [syntax tree enum]: Expr#syntax-tree-enums #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))] pub enum WherePredicate { /// A type predicate in a `where` clause: `for<'c> Foo<'c>: Trait<'c>`. diff --git a/src/group.rs b/src/group.rs index 2099906dca..6b05710154 100644 --- a/src/group.rs +++ b/src/group.rs @@ -137,12 +137,12 @@ fn parse_delimited<'a>( macro_rules! parenthesized { ($content:ident in $cursor:expr) => { match $crate::group::parse_parens(&$cursor) { - $crate::export::Ok(parens) => { + $crate::__private::Ok(parens) => { $content = parens.content; parens.token } - $crate::export::Err(error) => { - return $crate::export::Err(error); + $crate::__private::Err(error) => { + return $crate::__private::Err(error); } } }; @@ -215,12 +215,12 @@ macro_rules! parenthesized { macro_rules! braced { ($content:ident in $cursor:expr) => { match $crate::group::parse_braces(&$cursor) { - $crate::export::Ok(braces) => { + $crate::__private::Ok(braces) => { $content = braces.content; braces.token } - $crate::export::Err(error) => { - return $crate::export::Err(error); + $crate::__private::Err(error) => { + return $crate::__private::Err(error); } } }; @@ -270,12 +270,12 @@ macro_rules! braced { macro_rules! bracketed { ($content:ident in $cursor:expr) => { match $crate::group::parse_brackets(&$cursor) { - $crate::export::Ok(brackets) => { + $crate::__private::Ok(brackets) => { $content = brackets.content; brackets.token } - $crate::export::Err(error) => { - return $crate::export::Err(error); + $crate::__private::Err(error) => { + return $crate::__private::Err(error); } } }; diff --git a/src/item.rs b/src/item.rs index 4041c53950..5cf501b92a 100644 --- a/src/item.rs +++ b/src/item.rs @@ -15,10 +15,7 @@ ast_enum_of_structs! { /// /// This type is a [syntax tree enum]. /// - /// [syntax tree enum]: enum.Expr.html#syntax-tree-enums - // - // TODO: change syntax-tree-enum link to an intra rustdoc link, currently - // blocked on https://github.com/rust-lang/rust/issues/62833 + /// [syntax tree enum]: Expr#syntax-tree-enums #[cfg_attr(doc_cfg, doc(cfg(feature = "full")))] pub enum Item { /// A constant item: `const MAX: u16 = 65535`. @@ -74,8 +71,31 @@ ast_enum_of_structs! { /// Tokens forming an item not interpreted by Syn. Verbatim(TokenStream), + // The following is the only supported idiom for exhaustive matching of + // this enum. + // + // match expr { + // Item::Const(e) => {...} + // Item::Enum(e) => {...} + // ... + // Item::Verbatim(e) => {...} + // + // #[cfg(test)] + // Item::__TestExhaustive(_) => unimplemented!(), + // #[cfg(not(test))] + // _ => { /* some sane fallback */ } + // } + // + // This way we fail your tests but don't break your library when adding + // a variant. You will be notified by a test failure when a variant is + // added, so that you can add code to handle it, but your library will + // continue to compile and work for downstream users in the interim. + // + // Once `deny(reachable)` is available in rustc, Item will be + // reimplemented as a non_exhaustive enum. + // https://github.com/rust-lang/rust/issues/44109#issuecomment-521781237 #[doc(hidden)] - __Nonexhaustive, + __TestExhaustive(crate::private), } } @@ -360,7 +380,11 @@ impl Item { | Item::Macro(ItemMacro { attrs, .. }) | Item::Macro2(ItemMacro2 { attrs, .. }) => mem::replace(attrs, new), Item::Verbatim(_) => Vec::new(), - Item::__Nonexhaustive => unreachable!(), + + #[cfg(test)] + Item::__TestExhaustive(_) => unimplemented!(), + #[cfg(not(test))] + _ => unreachable!(), } } } @@ -454,10 +478,7 @@ ast_enum_of_structs! { /// /// This type is a [syntax tree enum]. /// - /// [syntax tree enum]: enum.Expr.html#syntax-tree-enums - // - // TODO: change syntax-tree-enum link to an intra rustdoc link, currently - // blocked on https://github.com/rust-lang/rust/issues/62833 + /// [syntax tree enum]: Expr#syntax-tree-enums #[cfg_attr(doc_cfg, doc(cfg(feature = "full")))] pub enum UseTree { /// A path prefix of imports in a `use` item: `std::...`. @@ -541,10 +562,7 @@ ast_enum_of_structs! { /// /// This type is a [syntax tree enum]. /// - /// [syntax tree enum]: enum.Expr.html#syntax-tree-enums - // - // TODO: change syntax-tree-enum link to an intra rustdoc link, currently - // blocked on https://github.com/rust-lang/rust/issues/62833 + /// [syntax tree enum]: Expr#syntax-tree-enums #[cfg_attr(doc_cfg, doc(cfg(feature = "full")))] pub enum ForeignItem { /// A foreign function in an `extern` block. @@ -562,8 +580,31 @@ ast_enum_of_structs! { /// Tokens in an `extern` block not interpreted by Syn. Verbatim(TokenStream), + // The following is the only supported idiom for exhaustive matching of + // this enum. + // + // match expr { + // ForeignItem::Fn(e) => {...} + // ForeignItem::Static(e) => {...} + // ... + // ForeignItem::Verbatim(e) => {...} + // + // #[cfg(test)] + // ForeignItem::__TestExhaustive(_) => unimplemented!(), + // #[cfg(not(test))] + // _ => { /* some sane fallback */ } + // } + // + // This way we fail your tests but don't break your library when adding + // a variant. You will be notified by a test failure when a variant is + // added, so that you can add code to handle it, but your library will + // continue to compile and work for downstream users in the interim. + // + // Once `deny(reachable)` is available in rustc, ForeignItem will be + // reimplemented as a non_exhaustive enum. + // https://github.com/rust-lang/rust/issues/44109#issuecomment-521781237 #[doc(hidden)] - __Nonexhaustive, + __TestExhaustive(crate::private), } } @@ -632,10 +673,7 @@ ast_enum_of_structs! { /// /// This type is a [syntax tree enum]. /// - /// [syntax tree enum]: enum.Expr.html#syntax-tree-enums - // - // TODO: change syntax-tree-enum link to an intra rustdoc link, currently - // blocked on https://github.com/rust-lang/rust/issues/62833 + /// [syntax tree enum]: Expr#syntax-tree-enums #[cfg_attr(doc_cfg, doc(cfg(feature = "full")))] pub enum TraitItem { /// An associated constant within the definition of a trait. @@ -653,8 +691,31 @@ ast_enum_of_structs! { /// Tokens within the definition of a trait not interpreted by Syn. Verbatim(TokenStream), + // The following is the only supported idiom for exhaustive matching of + // this enum. + // + // match expr { + // TraitItem::Const(e) => {...} + // TraitItem::Method(e) => {...} + // ... + // TraitItem::Verbatim(e) => {...} + // + // #[cfg(test)] + // TraitItem::__TestExhaustive(_) => unimplemented!(), + // #[cfg(not(test))] + // _ => { /* some sane fallback */ } + // } + // + // This way we fail your tests but don't break your library when adding + // a variant. You will be notified by a test failure when a variant is + // added, so that you can add code to handle it, but your library will + // continue to compile and work for downstream users in the interim. + // + // Once `deny(reachable)` is available in rustc, TraitItem will be + // reimplemented as a non_exhaustive enum. + // https://github.com/rust-lang/rust/issues/44109#issuecomment-521781237 #[doc(hidden)] - __Nonexhaustive, + __TestExhaustive(crate::private), } } @@ -725,10 +786,7 @@ ast_enum_of_structs! { /// /// This type is a [syntax tree enum]. /// - /// [syntax tree enum]: enum.Expr.html#syntax-tree-enums - // - // TODO: change syntax-tree-enum link to an intra rustdoc link, currently - // blocked on https://github.com/rust-lang/rust/issues/62833 + /// [syntax tree enum]: Expr#syntax-tree-enums #[cfg_attr(doc_cfg, doc(cfg(feature = "full")))] pub enum ImplItem { /// An associated constant within an impl block. @@ -746,8 +804,31 @@ ast_enum_of_structs! { /// Tokens within an impl block not interpreted by Syn. Verbatim(TokenStream), + // The following is the only supported idiom for exhaustive matching of + // this enum. + // + // match expr { + // ImplItem::Const(e) => {...} + // ImplItem::Method(e) => {...} + // ... + // ImplItem::Verbatim(e) => {...} + // + // #[cfg(test)] + // ImplItem::__TestExhaustive(_) => unimplemented!(), + // #[cfg(not(test))] + // _ => { /* some sane fallback */ } + // } + // + // This way we fail your tests but don't break your library when adding + // a variant. You will be notified by a test failure when a variant is + // added, so that you can add code to handle it, but your library will + // continue to compile and work for downstream users in the interim. + // + // Once `deny(reachable)` is available in rustc, ImplItem will be + // reimplemented as a non_exhaustive enum. + // https://github.com/rust-lang/rust/issues/44109#issuecomment-521781237 #[doc(hidden)] - __Nonexhaustive, + __TestExhaustive(crate::private), } } @@ -1420,11 +1501,11 @@ pub mod parsing { abi, fn_token, ident, + generics, paren_token, inputs, - output, variadic, - generics, + output, }) } } @@ -1710,7 +1791,11 @@ pub mod parsing { ForeignItem::Type(item) => &mut item.attrs, ForeignItem::Macro(item) => &mut item.attrs, ForeignItem::Verbatim(_) => return Ok(item), - ForeignItem::__Nonexhaustive => unreachable!(), + + #[cfg(test)] + ForeignItem::__TestExhaustive(_) => unimplemented!(), + #[cfg(not(test))] + _ => unreachable!(), }; attrs.extend(item_attrs.drain(..)); *item_attrs = attrs; @@ -2047,14 +2132,14 @@ pub mod parsing { let mut supertraits = Punctuated::new(); if colon_token.is_some() { loop { - supertraits.push_value(input.parse()?); if input.peek(Token![where]) || input.peek(token::Brace) { break; } - supertraits.push_punct(input.parse()?); + supertraits.push_value(input.parse()?); if input.peek(Token![where]) || input.peek(token::Brace) { break; } + supertraits.push_punct(input.parse()?); } } @@ -2188,7 +2273,12 @@ pub mod parsing { TraitItem::Method(item) => &mut item.attrs, TraitItem::Type(item) => &mut item.attrs, TraitItem::Macro(item) => &mut item.attrs, - TraitItem::Verbatim(_) | TraitItem::__Nonexhaustive => unreachable!(), + TraitItem::Verbatim(_) => unreachable!(), + + #[cfg(test)] + TraitItem::__TestExhaustive(_) => unimplemented!(), + #[cfg(not(test))] + _ => unreachable!(), }; attrs.extend(item_attrs.drain(..)); *item_attrs = attrs; @@ -2387,15 +2477,26 @@ pub mod parsing { None }; - let first_ty: Type = input.parse()?; + let mut first_ty: Type = input.parse()?; let self_ty: Type; let trait_; let is_impl_for = input.peek(Token![for]); if is_impl_for { let for_token: Token![for] = input.parse()?; - if let Type::Path(TypePath { qself: None, path }) = first_ty { - trait_ = Some((polarity, path, for_token)); + let mut first_ty_ref = &first_ty; + while let Type::Group(ty) = first_ty_ref { + first_ty_ref = &ty.elem; + } + if let Type::Path(_) = first_ty_ref { + while let Type::Group(ty) = first_ty { + first_ty = *ty.elem; + } + if let Type::Path(TypePath { qself: None, path }) = first_ty { + trait_ = Some((polarity, path, for_token)); + } else { + unreachable!() + } } else { trait_ = None; } @@ -2508,7 +2609,11 @@ pub mod parsing { ImplItem::Type(item) => &mut item.attrs, ImplItem::Macro(item) => &mut item.attrs, ImplItem::Verbatim(_) => return Ok(item), - ImplItem::__Nonexhaustive => unreachable!(), + + #[cfg(test)] + ImplItem::__TestExhaustive(_) => unimplemented!(), + #[cfg(not(test))] + _ => unreachable!(), }; attrs.extend(item_attrs.drain(..)); *item_attrs = attrs; diff --git a/src/lib.rs b/src/lib.rs index 8c3ea217e0..0d5ff42708 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -//! [![github]](https://github.com/dtolnay/syn) [![crates-io]](https://crates.io/crates/syn) [![docs-rs]](https://docs.rs/syn) +//! [![github]](https://github.com/dtolnay/syn) [![crates-io]](https://crates.io/crates/syn) [![docs-rs]](crate) //! //! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github //! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust @@ -39,12 +39,12 @@ //! procedural macros enable only what they need, and do not pay in compile //! time for all the rest. //! -//! [`syn::File`]: struct.File.html -//! [`syn::Item`]: enum.Item.html -//! [`syn::Expr`]: enum.Expr.html -//! [`syn::Type`]: enum.Type.html -//! [`syn::DeriveInput`]: struct.DeriveInput.html -//! [parser functions]: parse/index.html +//! [`syn::File`]: File +//! [`syn::Item`]: Item +//! [`syn::Expr`]: Expr +//! [`syn::Type`]: Type +//! [`syn::DeriveInput`]: DeriveInput +//! [parser functions]: mod@parse //! //!
//! @@ -58,7 +58,7 @@ //! tokens, then hand some tokens back to the compiler to compile into the //! user's crate. //! -//! [`TokenStream`]: https://doc.rust-lang.org/proc_macro/struct.TokenStream.html +//! [`TokenStream`]: proc_macro::TokenStream //! //! ```toml //! [dependencies] @@ -250,25 +250,21 @@ //! dynamic library libproc_macro from rustc toolchain. // Syn types in rustdoc of other crates get linked to here. -#![doc(html_root_url = "https://docs.rs/syn/1.0.53")] +#![doc(html_root_url = "https://docs.rs/syn/1.0.62")] #![cfg_attr(doc_cfg, feature(doc_cfg))] -#![deny(clippy::all, clippy::pedantic)] +#![allow(non_camel_case_types)] // Ignored clippy lints. #![allow( - clippy::blocks_in_if_conditions, - clippy::cognitive_complexity, clippy::doc_markdown, clippy::eval_order_dependence, clippy::inherent_to_string, clippy::large_enum_variant, - clippy::manual_non_exhaustive, - clippy::manual_strip, - clippy::match_like_matches_macro, + clippy::manual_map, // https://github.com/rust-lang/rust-clippy/issues/6795 clippy::match_on_vec_items, + clippy::missing_panics_doc, clippy::needless_doctest_main, clippy::needless_pass_by_value, clippy::never_loop, - clippy::suspicious_op_assign_impl, clippy::too_many_arguments, clippy::trivially_copy_pass_by_ref, clippy::unnecessary_unwrap @@ -280,18 +276,17 @@ clippy::empty_enum, clippy::expl_impl_clone_on_copy, clippy::if_not_else, - clippy::items_after_statements, clippy::match_same_arms, clippy::missing_errors_doc, clippy::module_name_repetitions, clippy::must_use_candidate, clippy::option_if_let_else, + clippy::redundant_else, clippy::shadow_unrelated, clippy::similar_names, clippy::single_match_else, clippy::too_many_lines, clippy::unseparated_literal_suffix, - clippy::use_self, clippy::used_underscore_binding, clippy::wildcard_imports )] @@ -796,7 +791,8 @@ pub use crate::gen::*; // Not public API. #[doc(hidden)] -pub mod export; +#[path = "export.rs"] +pub mod __private; mod custom_keyword; mod custom_punctuation; @@ -820,10 +816,9 @@ mod verbatim; #[cfg(all(any(feature = "full", feature = "derive"), feature = "printing"))] mod print; -//////////////////////////////////////////////////////////////////////////////// +use crate::__private::private; -#[allow(dead_code, non_camel_case_types)] -struct private; +//////////////////////////////////////////////////////////////////////////////// // https://github.com/rust-lang/rust/issues/62830 #[cfg(feature = "parsing")] diff --git a/src/lifetime.rs b/src/lifetime.rs index 9cec5eec3a..28cd7e3622 100644 --- a/src/lifetime.rs +++ b/src/lifetime.rs @@ -57,6 +57,17 @@ impl Lifetime { ident: Ident::new(&symbol[1..], span), } } + + pub fn span(&self) -> Span { + self.apostrophe + .join(self.ident.span()) + .unwrap_or(self.apostrophe) + } + + pub fn set_span(&mut self, span: Span) { + self.apostrophe = span; + self.ident.set_span(span); + } } impl Display for Lifetime { diff --git a/src/lit.rs b/src/lit.rs index 3780648665..0aa50a590c 100644 --- a/src/lit.rs +++ b/src/lit.rs @@ -21,10 +21,7 @@ ast_enum_of_structs! { /// /// This type is a [syntax tree enum]. /// - /// [syntax tree enum]: enum.Expr.html#syntax-tree-enums - // - // TODO: change syntax-tree-enum link to an intra rustdoc link, currently - // blocked on https://github.com/rust-lang/rust/issues/62833 + /// [syntax tree enum]: crate::Expr#syntax-tree-enums pub enum Lit { /// A UTF-8 string literal: `"foo"`. Str(LitStr), @@ -508,6 +505,24 @@ impl Display for LitFloat { } } +impl LitBool { + pub fn new(value: bool, span: Span) -> Self { + LitBool { value, span } + } + + pub fn value(&self) -> bool { + self.value + } + + pub fn span(&self) -> Span { + self.span + } + + pub fn set_span(&mut self, span: Span) { + self.span = span; + } +} + #[cfg(feature = "extra-traits")] mod debug_impls { use super::*; diff --git a/src/macros.rs b/src/macros.rs index e0d0b81f88..5097da9481 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -77,7 +77,7 @@ macro_rules! ast_enum_of_structs_impl { $pub:ident $enum:ident $name:ident { $( $(#[$variant_attr:meta])* - $variant:ident $( ($member:ident) )*, + $variant:ident $( ($($member:ident)::+) )*, )* } @@ -87,7 +87,7 @@ macro_rules! ast_enum_of_structs_impl { check_keyword_matches!(enum $enum); $($( - ast_enum_from_struct!($name::$variant, $member); + ast_enum_from_struct!($name::$variant, $($member)::+); )*)* #[cfg(feature = "printing")] @@ -95,7 +95,7 @@ macro_rules! ast_enum_of_structs_impl { $($remaining)* () tokens - $name { $($variant $($member)*,)* } + $name { $($variant $($($member)::+)*,)* } } }; } @@ -104,6 +104,9 @@ macro_rules! ast_enum_from_struct { // No From for verbatim variants. ($name:ident::Verbatim, $member:ident) => {}; + // No From for private variants. + ($name:ident::$variant:ident, crate::private) => {}; + ($name:ident::$variant:ident, $member:ident) => { impl From<$member> for $name { fn from(e: $member) -> $name { @@ -131,6 +134,13 @@ macro_rules! generate_to_tokens { ); }; + (($($arms:tt)*) $tokens:ident $name:ident { $variant:ident crate::private, $($next:tt)*}) => { + generate_to_tokens!( + ($($arms)* $name::$variant(_) => unreachable!(),) + $tokens $name { $($next)* } + ); + }; + (($($arms:tt)*) $tokens:ident $name:ident {}) => { #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))] impl ::quote::ToTokens for $name { diff --git a/src/parse.rs b/src/parse.rs index f771daf367..d85968bd24 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -6,9 +6,8 @@ //! [`Cursor`] type. `Cursor` is a cheaply copyable cursor over a range of //! tokens in a token stream. //! -//! [`ParseStream`]: type.ParseStream.html -//! [`Result`]: type.Result.html -//! [`Cursor`]: ../buffer/index.html +//! [`Result`]: Result +//! [`Cursor`]: crate::buffer::Cursor //! //! # Example //! @@ -23,7 +22,7 @@ //! procedural macro, they will receive a helpful compiler error message //! pointing out the exact token that triggered the failure to parse. //! -//! [`parse_macro_input!`]: ../macro.parse_macro_input.html +//! [`parse_macro_input!`]: crate::parse_macro_input! //! //! ``` //! # extern crate proc_macro; @@ -96,10 +95,9 @@ //! obvious default way. These functions can return any syntax tree node that //! implements the [`Parse`] trait, which includes most types in Syn. //! -//! [`syn::parse`]: ../fn.parse.html -//! [`syn::parse2`]: ../fn.parse2.html -//! [`syn::parse_str`]: ../fn.parse_str.html -//! [`Parse`]: trait.Parse.html +//! [`syn::parse`]: crate::parse() +//! [`syn::parse2`]: crate::parse2() +//! [`syn::parse_str`]: crate::parse_str() //! //! ``` //! use syn::Type; @@ -114,7 +112,7 @@ //! //! The [`parse_quote!`] macro also uses this approach. //! -//! [`parse_quote!`]: ../macro.parse_quote.html +//! [`parse_quote!`]: crate::parse_quote! //! //! # The `Parser` trait //! @@ -124,8 +122,8 @@ //! may or may not allow trailing punctuation, and parsing it the wrong way //! would either reject valid input or accept invalid input. //! -//! [`Attribute`]: ../struct.Attribute.html -//! [`Punctuated`]: ../punctuated/index.html +//! [`Attribute`]: crate::Attribute +//! [`Punctuated`]: crate::punctuated //! //! The `Parse` trait is not implemented in these cases because there is no good //! behavior to consider the default. @@ -150,7 +148,6 @@ //! single `Parse` implementation, and those parser functions can be invoked //! through the [`Parser`] trait. //! -//! [`Parser`]: trait.Parser.html //! //! ``` //! # extern crate proc_macro; @@ -248,7 +245,7 @@ pub type ParseStream<'a> = &'a ParseBuffer<'a>; /// - One of [the `syn::parse*` functions][syn-parse]; or /// - A method of the [`Parser`] trait. /// -/// [syn-parse]: index.html#the-synparse-functions +/// [syn-parse]: self#the-synparse-functions pub struct ParseBuffer<'a> { scope: Span, // Instead of Cell> so that ParseBuffer<'a> is covariant in 'a. diff --git a/src/parse_macro_input.rs b/src/parse_macro_input.rs index 6135b45c1b..8e1a5ec6b9 100644 --- a/src/parse_macro_input.rs +++ b/src/parse_macro_input.rs @@ -108,17 +108,17 @@ macro_rules! parse_macro_input { ($tokenstream:ident as $ty:ty) => { match $crate::parse_macro_input::parse::<$ty>($tokenstream) { - $crate::export::Ok(data) => data, - $crate::export::Err(err) => { - return $crate::export::TokenStream::from(err.to_compile_error()); + $crate::__private::Ok(data) => data, + $crate::__private::Err(err) => { + return $crate::__private::TokenStream::from(err.to_compile_error()); } } }; ($tokenstream:ident with $parser:path) => { match $crate::parse::Parser::parse($parser, $tokenstream) { - $crate::export::Ok(data) => data, - $crate::export::Err(err) => { - return $crate::export::TokenStream::from(err.to_compile_error()); + $crate::__private::Ok(data) => data, + $crate::__private::Err(err) => { + return $crate::__private::TokenStream::from(err.to_compile_error()); } } }; diff --git a/src/parse_quote.rs b/src/parse_quote.rs index 412b06f35f..ec551ef973 100644 --- a/src/parse_quote.rs +++ b/src/parse_quote.rs @@ -74,8 +74,8 @@ macro_rules! parse_quote { ($($tt:tt)*) => { $crate::parse_quote::parse( - $crate::export::From::from( - $crate::export::quote::quote!($($tt)*) + $crate::__private::From::from( + $crate::__private::quote::quote!($($tt)*) ) ) }; diff --git a/src/pat.rs b/src/pat.rs index 541e9823c3..312d3c08de 100644 --- a/src/pat.rs +++ b/src/pat.rs @@ -12,10 +12,7 @@ ast_enum_of_structs! { /// /// This type is a [syntax tree enum]. /// - /// [syntax tree enum]: enum.Expr.html#syntax-tree-enums - // - // TODO: change syntax-tree-enum link to an intra rustdoc link, currently - // blocked on https://github.com/rust-lang/rust/issues/62833 + /// [syntax tree enum]: Expr#syntax-tree-enums #[cfg_attr(doc_cfg, doc(cfg(feature = "full")))] pub enum Pat { /// A box pattern: `box v`. @@ -75,8 +72,31 @@ ast_enum_of_structs! { /// A pattern that matches any value: `_`. Wild(PatWild), + // The following is the only supported idiom for exhaustive matching of + // this enum. + // + // match expr { + // Pat::Box(e) => {...} + // Pat::Ident(e) => {...} + // ... + // Pat::Wild(e) => {...} + // + // #[cfg(test)] + // Pat::__TestExhaustive(_) => unimplemented!(), + // #[cfg(not(test))] + // _ => { /* some sane fallback */ } + // } + // + // This way we fail your tests but don't break your library when adding + // a variant. You will be notified by a test failure when a variant is + // added, so that you can add code to handle it, but your library will + // continue to compile and work for downstream users in the interim. + // + // Once `deny(reachable)` is available in rustc, Pat will be + // reimplemented as a non_exhaustive enum. + // https://github.com/rust-lang/rust/issues/44109#issuecomment-521781237 #[doc(hidden)] - __Nonexhaustive, + __TestExhaustive(crate::private), } } diff --git a/src/path.rs b/src/path.rs index a2b42241e1..3f668c9deb 100644 --- a/src/path.rs +++ b/src/path.rs @@ -241,16 +241,29 @@ pub mod parsing { if input.peek(Ident) && input.peek2(Token![:]) && !input.peek2(Token![::]) { return Ok(GenericArgument::Constraint(input.parse()?)); } + } - if input.peek(Lit) { - let lit = input.parse()?; - return Ok(GenericArgument::Const(Expr::Lit(lit))); - } + if input.peek(Lit) { + let lit = input.parse()?; + return Ok(GenericArgument::Const(Expr::Lit(lit))); + } - if input.peek(token::Brace) { + if input.peek(token::Brace) { + #[cfg(feature = "full")] + { let block = input.call(expr::parsing::expr_block)?; return Ok(GenericArgument::Const(Expr::Block(block))); } + + #[cfg(not(feature = "full"))] + { + let begin = input.fork(); + let content; + braced!(content in input); + content.parse::()?; + let verbatim = verbatim::between(begin, input); + return Ok(GenericArgument::Const(Expr::Verbatim(verbatim))); + } } input.parse().map(GenericArgument::Type) @@ -499,22 +512,32 @@ pub mod parsing { } } - fn parse_helper(input: ParseStream, expr_style: bool) -> Result { - Ok(Path { + pub(crate) fn parse_helper(input: ParseStream, expr_style: bool) -> Result { + let mut path = Path { leading_colon: input.parse()?, segments: { let mut segments = Punctuated::new(); let value = PathSegment::parse_helper(input, expr_style)?; segments.push_value(value); - while input.peek(Token![::]) { - let punct: Token![::] = input.parse()?; - segments.push_punct(punct); - let value = PathSegment::parse_helper(input, expr_style)?; - segments.push_value(value); - } segments }, - }) + }; + Path::parse_rest(input, &mut path, expr_style)?; + Ok(path) + } + + pub(crate) fn parse_rest( + input: ParseStream, + path: &mut Self, + expr_style: bool, + ) -> Result<()> { + while input.peek(Token![::]) { + let punct: Token![::] = input.parse()?; + path.segments.push_punct(punct); + let value = PathSegment::parse_helper(input, expr_style)?; + path.segments.push_value(value); + } + Ok(()) } } @@ -724,7 +747,7 @@ mod printing { } impl private { - pub fn print_path(tokens: &mut TokenStream, qself: &Option, path: &Path) { + pub(crate) fn print_path(tokens: &mut TokenStream, qself: &Option, path: &Path) { let qself = match qself { Some(qself) => qself, None => { diff --git a/src/punctuated.rs b/src/punctuated.rs index 595922c61e..f94edc2915 100644 --- a/src/punctuated.rs +++ b/src/punctuated.rs @@ -13,7 +13,7 @@ //! syntax tree node + punctuation, where every node in the sequence is followed //! by punctuation except for possibly the final one. //! -//! [`Punctuated`]: struct.Punctuated.html +//! [`Punctuated`]: Punctuated //! //! ```text //! a_function_call(arg1, arg2, arg3); @@ -50,7 +50,17 @@ pub struct Punctuated { impl Punctuated { /// Creates an empty punctuated sequence. - pub fn new() -> Punctuated { + #[cfg(not(syn_no_const_vec_new))] + pub const fn new() -> Self { + Punctuated { + inner: Vec::new(), + last: None, + } + } + + /// Creates an empty punctuated sequence. + #[cfg(syn_no_const_vec_new)] + pub fn new() -> Self { Punctuated { inner: Vec::new(), last: None, @@ -152,7 +162,11 @@ impl Punctuated { /// Panics if the sequence does not already have a trailing punctuation when /// this method is called. pub fn push_value(&mut self, value: T) { - assert!(self.empty_or_trailing()); + assert!( + self.empty_or_trailing(), + "Punctuated::push_value: cannot push value if Punctuated is missing trailing punctuation", + ); + self.last = Some(Box::new(value)); } @@ -164,7 +178,11 @@ impl Punctuated { /// /// Panics if the sequence is empty or already has a trailing punctuation. pub fn push_punct(&mut self, punctuation: P) { - assert!(self.last.is_some()); + assert!( + self.last.is_some(), + "Punctuated::push_punct: cannot push punctuation if Punctuated is empty or already has trailing punctuation", + ); + let last = self.last.take().unwrap(); self.inner.push((*last, punctuation)); } @@ -218,7 +236,10 @@ impl Punctuated { where P: Default, { - assert!(index <= self.len()); + assert!( + index <= self.len(), + "Punctuated::insert: index out of range", + ); if index == self.len() { self.push(value); @@ -444,7 +465,11 @@ impl FromIterator> for Punctuated { impl Extend> for Punctuated { fn extend>>(&mut self, i: I) { - assert!(self.empty_or_trailing()); + assert!( + self.empty_or_trailing(), + "Punctuated::extend: Punctuated is not empty or does not have a trailing punctuation", + ); + let mut nomore = false; for pair in i { if nomore { diff --git a/src/spanned.rs b/src/spanned.rs index 01591cedcb..d51ffb3fa5 100644 --- a/src/spanned.rs +++ b/src/spanned.rs @@ -13,8 +13,8 @@ //! of a struct for which we are deriving a trait implementation, and we need to //! be able to pass a reference to one of those fields across threads. //! -//! [`Type`]: ../enum.Type.html -//! [`Sync`]: https://doc.rust-lang.org/std/marker/trait.Sync.html +//! [`Type`]: crate::Type +//! [`Sync`]: std::marker::Sync //! //! If the field type does *not* implement `Sync` as required, we want the //! compiler to report an error pointing out exactly which type it was. diff --git a/src/token.rs b/src/token.rs index 4bedbbb0ac..2208b07d04 100644 --- a/src/token.rs +++ b/src/token.rs @@ -4,13 +4,13 @@ //! prefer to use the [`Token!`] macro instead. This is a type-macro that //! expands to the token type of the given token. //! -//! [`Token!`]: ../macro.Token.html +//! [`Token!`]: crate::Token //! //! # Example //! //! The [`ItemStatic`] syntax tree node is defined like this. //! -//! [`ItemStatic`]: ../struct.ItemStatic.html +//! [`ItemStatic`]: crate::ItemStatic //! //! ``` //! # use syn::{Attribute, Expr, Ident, Token, Type, Visibility}; @@ -35,10 +35,10 @@ //! method. Delimiter tokens are parsed using the [`parenthesized!`], //! [`bracketed!`] and [`braced!`] macros. //! -//! [`ParseStream::parse`]: ../parse/struct.ParseBuffer.html#method.parse -//! [`parenthesized!`]: ../macro.parenthesized.html -//! [`bracketed!`]: ../macro.bracketed.html -//! [`braced!`]: ../macro.braced.html +//! [`ParseStream::parse`]: crate::parse::ParseBuffer::parse() +//! [`parenthesized!`]: crate::parenthesized! +//! [`bracketed!`]: crate::bracketed! +//! [`braced!`]: crate::braced! //! //! ``` //! use syn::{Attribute, Result}; @@ -83,8 +83,8 @@ //! //! - Field access to its span — `let sp = the_token.span` //! -//! [Peeking]: ../parse/struct.ParseBuffer.html#method.peek -//! [Parsing]: ../parse/struct.ParseBuffer.html#method.parse +//! [Peeking]: crate::parse::ParseBuffer::peek() +//! [Parsing]: crate::parse::ParseBuffer::parse() //! [Printing]: https://docs.rs/quote/1.0/quote/trait.ToTokens.html //! [`Span`]: https://docs.rs/proc-macro2/1.0/proc_macro2/struct.Span.html diff --git a/src/ty.rs b/src/ty.rs index 4f995d5a19..b60109f9ee 100644 --- a/src/ty.rs +++ b/src/ty.rs @@ -12,10 +12,7 @@ ast_enum_of_structs! { /// /// This type is a [syntax tree enum]. /// - /// [syntax tree enum]: enum.Expr.html#syntax-tree-enums - // - // TODO: change syntax-tree-enum link to an intra rustdoc link, currently - // blocked on https://github.com/rust-lang/rust/issues/62833 + /// [syntax tree enum]: Expr#syntax-tree-enums #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))] pub enum Type { /// A fixed size array type: `[T; n]`. @@ -66,8 +63,31 @@ ast_enum_of_structs! { /// Tokens in type position not interpreted by Syn. Verbatim(TokenStream), + // The following is the only supported idiom for exhaustive matching of + // this enum. + // + // match expr { + // Type::Array(e) => {...} + // Type::BareFn(e) => {...} + // ... + // Type::Verbatim(e) => {...} + // + // #[cfg(test)] + // Type::__TestExhaustive(_) => unimplemented!(), + // #[cfg(not(test))] + // _ => { /* some sane fallback */ } + // } + // + // This way we fail your tests but don't break your library when adding + // a variant. You will be notified by a test failure when a variant is + // added, so that you can add code to handle it, but your library will + // continue to compile and work for downstream users in the interim. + // + // Once `deny(reachable)` is available in rustc, Type will be + // reimplemented as a non_exhaustive enum. + // https://github.com/rust-lang/rust/issues/44109#issuecomment-521781237 #[doc(hidden)] - __Nonexhaustive, + __TestExhaustive(crate::private), } } @@ -345,11 +365,41 @@ pub mod parsing { } fn ambig_ty(input: ParseStream, allow_plus: bool) -> Result { - if input.peek(token::Group) && !input.peek2(Token![::]) && !input.peek2(Token![<]) { - return input.parse().map(Type::Group); + let begin = input.fork(); + + if input.peek(token::Group) { + let mut group: TypeGroup = input.parse()?; + if input.peek(Token![::]) && input.peek3(Ident::peek_any) { + if let Type::Path(mut ty) = *group.elem { + Path::parse_rest(input, &mut ty.path, false)?; + return Ok(Type::Path(ty)); + } else { + return Ok(Type::Path(TypePath { + qself: Some(QSelf { + lt_token: Token![<](group.group_token.span), + position: 0, + as_token: None, + gt_token: Token![>](group.group_token.span), + ty: group.elem, + }), + path: Path::parse_helper(input, false)?, + })); + } + } else if input.peek(Token![<]) || input.peek(Token![::]) && input.peek3(Token![<]) { + if let Type::Path(mut ty) = *group.elem { + let arguments = &mut ty.path.segments.last_mut().unwrap().arguments; + if let PathArguments::None = arguments { + *arguments = PathArguments::AngleBracketed(input.parse()?); + Path::parse_rest(input, &mut ty.path, false)?; + return Ok(Type::Path(ty)); + } else { + group.elem = Box::new(Type::Path(ty)); + } + } + } + return Ok(Type::Group(group)); } - let begin = input.fork(); let mut lifetimes = None::; let mut lookahead = input.lookahead1(); if lookahead.peek(Token![for]) { @@ -408,9 +458,13 @@ pub mod parsing { let mut elems = Punctuated::new(); elems.push_value(first); elems.push_punct(content.parse()?); - let rest: Punctuated = - content.parse_terminated(Parse::parse)?; - elems.extend(rest); + while !content.is_empty() { + elems.push_value(content.parse()?); + if content.is_empty() { + break; + } + elems.push_punct(content.parse()?); + } elems }, })); @@ -743,9 +797,13 @@ pub mod parsing { let mut elems = Punctuated::new(); elems.push_value(first); elems.push_punct(content.parse()?); - let rest: Punctuated = - content.parse_terminated(Parse::parse)?; - elems.extend(rest); + while !content.is_empty() { + elems.push_value(content.parse()?); + if content.is_empty() { + break; + } + elems.push_punct(content.parse()?); + } elems }, }) diff --git a/syn.json b/syn.json index 7e7a659b52..e82cdc1fc4 100644 --- a/syn.json +++ b/syn.json @@ -1,5 +1,5 @@ { - "version": "1.0.53", + "version": "1.0.62", "types": [ { "ident": "Abi", diff --git a/tests/common/eq.rs b/tests/common/eq.rs index 4783dd8367..51fb7c77cd 100644 --- a/tests/common/eq.rs +++ b/tests/common/eq.rs @@ -7,21 +7,21 @@ use rustc_ast::ast::{ AssocTyConstraintKind, Async, AttrId, AttrItem, AttrKind, AttrStyle, Attribute, BareFnTy, BinOpKind, BindingMode, Block, BlockCheckMode, BorrowKind, CaptureBy, Const, Crate, CrateSugar, Defaultness, EnumDef, Expr, ExprKind, Extern, Field, FieldPat, FloatTy, FnDecl, FnHeader, - FnRetTy, FnSig, ForeignItemKind, ForeignMod, GenericArg, GenericArgs, GenericBound, - GenericParam, GenericParamKind, Generics, GlobalAsm, ImplPolarity, InlineAsm, InlineAsmOperand, - InlineAsmOptions, InlineAsmRegOrRegClass, InlineAsmTemplatePiece, IntTy, IsAuto, Item, - ItemKind, Label, Lifetime, Lit, LitFloatType, LitIntType, LitKind, LlvmAsmDialect, - LlvmInlineAsm, LlvmInlineAsmOutput, Local, MacArgs, MacCall, MacCallStmt, MacDelimiter, - MacStmtStyle, MacroDef, Mod, Movability, MutTy, Mutability, NodeId, Param, ParenthesizedArgs, - Pat, PatKind, Path, PathSegment, PolyTraitRef, QSelf, RangeEnd, RangeLimits, RangeSyntax, Stmt, - StmtKind, StrLit, StrStyle, StructField, StructRest, TraitBoundModifier, TraitObjectSyntax, - TraitRef, Ty, TyKind, UintTy, UnOp, Unsafe, UnsafeSource, UseTree, UseTreeKind, Variant, - VariantData, Visibility, VisibilityKind, WhereBoundPredicate, WhereClause, WhereEqPredicate, - WherePredicate, WhereRegionPredicate, + FnKind, FnRetTy, FnSig, ForeignItemKind, ForeignMod, GenericArg, GenericArgs, GenericBound, + GenericParam, GenericParamKind, Generics, GlobalAsm, ImplKind, ImplPolarity, Inline, InlineAsm, + InlineAsmOperand, InlineAsmOptions, InlineAsmRegOrRegClass, InlineAsmTemplatePiece, IntTy, + IsAuto, Item, ItemKind, Label, Lifetime, Lit, LitFloatType, LitIntType, LitKind, + LlvmAsmDialect, LlvmInlineAsm, LlvmInlineAsmOutput, Local, MacArgs, MacCall, MacCallStmt, + MacDelimiter, MacStmtStyle, MacroDef, ModKind, Movability, MutTy, Mutability, NodeId, Param, + ParenthesizedArgs, Pat, PatKind, Path, PathSegment, PolyTraitRef, QSelf, RangeEnd, RangeLimits, + RangeSyntax, Stmt, StmtKind, StrLit, StrStyle, StructField, StructRest, TraitBoundModifier, + TraitKind, TraitObjectSyntax, TraitRef, Ty, TyAliasKind, TyKind, UintTy, UnOp, Unsafe, + UnsafeSource, UseTree, UseTreeKind, Variant, VariantData, Visibility, VisibilityKind, + WhereBoundPredicate, WhereClause, WhereEqPredicate, WherePredicate, WhereRegionPredicate, }; use rustc_ast::ptr::P; -use rustc_ast::token::{self, CommentKind, DelimToken, Token, TokenKind}; -use rustc_ast::tokenstream::{self, DelimSpan, LazyTokenStream, TokenStream, TokenTree}; +use rustc_ast::token::{self, CommentKind, DelimToken, Nonterminal, Token, TokenKind}; +use rustc_ast::tokenstream::{DelimSpan, LazyTokenStream, TokenStream, TokenTree}; use rustc_data_structures::sync::Lrc; use rustc_data_structures::thin_vec::ThinVec; use rustc_span::source_map::Spanned; @@ -32,6 +32,12 @@ pub trait SpanlessEq { fn eq(&self, other: &Self) -> bool; } +impl SpanlessEq for Box { + fn eq(&self, other: &Self) -> bool { + SpanlessEq::eq(&**self, &**other) + } +} + impl SpanlessEq for P { fn eq(&self, other: &Self) -> bool { SpanlessEq::eq(&**self, &**other) @@ -129,47 +135,47 @@ spanless_eq_partial_eq!(token::LitKind); macro_rules! spanless_eq_struct { { - $($name:ident)::+ $(<$param:ident>)?; - $([$field:ident $other:ident])* - $(![$ignore:ident])* + $($name:ident)::+ $(<$param:ident>)? + $([$field:tt $this:ident $other:ident])* + $(![$ignore:tt])*; } => { impl $(<$param: SpanlessEq>)* SpanlessEq for $($name)::+ $(<$param>)* { fn eq(&self, other: &Self) -> bool { - let $($name)::+ { $($field,)* $($ignore: _,)* } = self; + let $($name)::+ { $($field: $this,)* $($ignore: _,)* } = self; let $($name)::+ { $($field: $other,)* $($ignore: _,)* } = other; - true $(&& SpanlessEq::eq($field, $other))* + true $(&& SpanlessEq::eq($this, $other))* } } }; { - $($name:ident)::+ $(<$param:ident>)?; - $([$field:ident $other:ident])* - $(![$ignore:ident])* - $next:ident + $($name:ident)::+ $(<$param:ident>)? + $([$field:tt $this:ident $other:ident])* + $(![$ignore:tt])*; + !$next:tt $($rest:tt)* } => { spanless_eq_struct! { - $($name)::+ $(<$param>)*; - $([$field $other])* - [$next other] + $($name)::+ $(<$param>)* + $([$field $this $other])* $(![$ignore])* + ![$next]; $($rest)* } }; { - $($name:ident)::+ $(<$param:ident>)?; - $([$field:ident $other:ident])* - $(![$ignore:ident])* - !$next:ident + $($name:ident)::+ $(<$param:ident>)? + $([$field:tt $this:ident $other:ident])* + $(![$ignore:tt])*; + $next:tt $($rest:tt)* } => { spanless_eq_struct! { - $($name)::+ $(<$param>)*; - $([$field $other])* - $(![$ignore])* - ![$next] + $($name)::+ $(<$param>)* + $([$field $this $other])* + [$next this other] + $(![$ignore])*; $($rest)* } }; @@ -282,18 +288,20 @@ spanless_eq_struct!(AttrItem; path args tokens); spanless_eq_struct!(Attribute; kind id style span); spanless_eq_struct!(BareFnTy; unsafety ext generic_params decl); spanless_eq_struct!(Block; stmts id rules span tokens); -spanless_eq_struct!(Crate; module attrs span proc_macros); +spanless_eq_struct!(Crate; attrs items span proc_macros); spanless_eq_struct!(EnumDef; variants); spanless_eq_struct!(Expr; id kind span attrs !tokens); spanless_eq_struct!(Field; attrs id span ident expr is_shorthand is_placeholder); spanless_eq_struct!(FieldPat; ident pat is_shorthand attrs id span is_placeholder); spanless_eq_struct!(FnDecl; inputs output); spanless_eq_struct!(FnHeader; constness asyncness unsafety ext); +spanless_eq_struct!(FnKind; 0 1 2 3); spanless_eq_struct!(FnSig; header decl span); spanless_eq_struct!(ForeignMod; unsafety abi items); spanless_eq_struct!(GenericParam; id ident attrs bounds is_placeholder kind); spanless_eq_struct!(Generics; params where_clause span); spanless_eq_struct!(GlobalAsm; asm); +spanless_eq_struct!(ImplKind; unsafety polarity defaultness constness generics of_trait self_ty items); spanless_eq_struct!(InlineAsm; template operands options line_spans); spanless_eq_struct!(Item; attrs id span vis ident kind !tokens); spanless_eq_struct!(Label; ident); @@ -305,9 +313,8 @@ spanless_eq_struct!(Local; pat ty init id span attrs !tokens); spanless_eq_struct!(MacCall; path args prior_type_ascription); spanless_eq_struct!(MacCallStmt; mac style attrs tokens); spanless_eq_struct!(MacroDef; body macro_rules); -spanless_eq_struct!(Mod; inner unsafety items inline); spanless_eq_struct!(MutTy; ty mutbl); -spanless_eq_struct!(ParenthesizedArgs; span inputs output); +spanless_eq_struct!(ParenthesizedArgs; span inputs inputs_span output); spanless_eq_struct!(Pat; id kind span tokens); spanless_eq_struct!(Path; span segments tokens); spanless_eq_struct!(PathSegment; ident id args); @@ -317,8 +324,10 @@ spanless_eq_struct!(Stmt; id kind span); spanless_eq_struct!(StrLit; style symbol suffix span symbol_unescaped); spanless_eq_struct!(StructField; attrs id span vis ident ty is_placeholder); spanless_eq_struct!(Token; kind span); +spanless_eq_struct!(TraitKind; 0 1 2 3 4); spanless_eq_struct!(TraitRef; path ref_id); spanless_eq_struct!(Ty; id kind span tokens); +spanless_eq_struct!(TyAliasKind; 0 1 2 3); spanless_eq_struct!(UseTree; prefix kind span); spanless_eq_struct!(Variant; attrs id span !vis ident data disr_expr is_placeholder); spanless_eq_struct!(Visibility; kind span tokens); @@ -328,7 +337,7 @@ spanless_eq_struct!(WhereEqPredicate; id span lhs_ty rhs_ty); spanless_eq_struct!(WhereRegionPredicate; span lifetime bounds); spanless_eq_struct!(token::Lit; kind symbol suffix); spanless_eq_enum!(AngleBracketedArg; Arg(0) Constraint(0)); -spanless_eq_enum!(AssocItemKind; Const(0 1 2) Fn(0 1 2 3) TyAlias(0 1 2 3) MacCall(0)); +spanless_eq_enum!(AssocItemKind; Const(0 1 2) Fn(0) TyAlias(0) MacCall(0)); spanless_eq_enum!(AssocTyConstraintKind; Equality(ty) Bound(bounds)); spanless_eq_enum!(Async; Yes(span closure_id return_impl_trait_id) No); spanless_eq_enum!(AttrStyle; Outer Inner); @@ -343,12 +352,13 @@ spanless_eq_enum!(Defaultness; Default(0) Final); spanless_eq_enum!(Extern; None Implicit Explicit(0)); spanless_eq_enum!(FloatTy; F32 F64); spanless_eq_enum!(FnRetTy; Default(0) Ty(0)); -spanless_eq_enum!(ForeignItemKind; Static(0 1 2) Fn(0 1 2 3) TyAlias(0 1 2 3) MacCall(0)); +spanless_eq_enum!(ForeignItemKind; Static(0 1 2) Fn(0) TyAlias(0) MacCall(0)); spanless_eq_enum!(GenericArg; Lifetime(0) Type(0) Const(0)); spanless_eq_enum!(GenericArgs; AngleBracketed(0) Parenthesized(0)); spanless_eq_enum!(GenericBound; Trait(0 1) Outlives(0)); -spanless_eq_enum!(GenericParamKind; Lifetime Type(default) Const(ty kw_span)); +spanless_eq_enum!(GenericParamKind; Lifetime Type(default) Const(ty kw_span default)); spanless_eq_enum!(ImplPolarity; Positive Negative(0)); +spanless_eq_enum!(Inline; Yes No); spanless_eq_enum!(InlineAsmRegOrRegClass; Reg(0) RegClass(0)); spanless_eq_enum!(InlineAsmTemplatePiece; String(0) Placeholder(operand_idx modifier span)); spanless_eq_enum!(IntTy; Isize I8 I16 I32 I64 I128); @@ -359,6 +369,7 @@ spanless_eq_enum!(LlvmAsmDialect; Att Intel); spanless_eq_enum!(MacArgs; Empty Delimited(0 1 2) Eq(0 1)); spanless_eq_enum!(MacDelimiter; Parenthesis Bracket Brace); spanless_eq_enum!(MacStmtStyle; Semicolon Braces NoBraces); +spanless_eq_enum!(ModKind; Loaded(0 1 2) Unloaded); spanless_eq_enum!(Movability; Static Movable); spanless_eq_enum!(Mutability; Mut Not); spanless_eq_enum!(RangeEnd; Included(0) Excluded); @@ -389,10 +400,8 @@ spanless_eq_enum!(InlineAsmOperand; In(reg expr) Out(reg late expr) InOut(reg late expr) SplitInOut(reg late in_expr out_expr) Const(expr) Sym(expr)); spanless_eq_enum!(ItemKind; ExternCrate(0) Use(0) Static(0 1 2) Const(0 1 2) - Fn(0 1 2 3) Mod(0) ForeignMod(0) GlobalAsm(0) TyAlias(0 1 2 3) Enum(0 1) - Struct(0 1) Union(0 1) Trait(0 1 2 3 4) TraitAlias(0 1) - Impl(unsafety polarity defaultness constness generics of_trait self_ty items) - MacCall(0) MacroDef(0)); + Fn(0) Mod(0 1) ForeignMod(0) GlobalAsm(0) TyAlias(0) Enum(0 1) Struct(0 1) + Union(0 1) Trait(0) TraitAlias(0 1) Impl(0) MacCall(0) MacroDef(0)); spanless_eq_enum!(LitKind; Str(0 1) ByteStr(0) Byte(0) Char(0) Int(0 1) Float(0 1) Bool(0) Err(0)); spanless_eq_enum!(PatKind; Wild Ident(0 1 2) Struct(0 1 2) TupleStruct(0 1) @@ -452,6 +461,14 @@ impl SpanlessEq for TokenKind { TokenKind::DotDotEq | TokenKind::DotDotDot => true, _ => false, }, + (TokenKind::Interpolated(this), TokenKind::Interpolated(other)) => { + match (this.as_ref(), other.as_ref()) { + (Nonterminal::NtExpr(this), Nonterminal::NtExpr(other)) => { + SpanlessEq::eq(this, other) + } + _ => this == other, + } + } _ => self == other, } } @@ -530,34 +547,42 @@ fn doc_comment<'a>( })) => {} _ => return false, } - is_escaped_literal(trees, unescaped) + match trees.next() { + Some(TokenTree::Token(token)) => { + is_escaped_literal(token, unescaped) && trees.next().is_none() + } + _ => false, + } } -fn is_escaped_literal(mut trees: tokenstream::CursorRef, unescaped: Symbol) -> bool { - match trees.next() { - Some(TokenTree::Token(Token { - kind: - TokenKind::Literal( - lit - @ - token::Lit { - kind: token::LitKind::Str, - symbol: _, - suffix: None, - }, - ), +fn is_escaped_literal(token: &Token, unescaped: Symbol) -> bool { + match match token { + Token { + kind: TokenKind::Literal(lit), span: _, - })) => match Lit::from_lit_token(*lit, DUMMY_SP) { - Ok(Lit { - token: _, - kind: LitKind::Str(symbol, StrStyle::Cooked), - span: _, - }) => { - symbol.as_str().replace('\r', "") == unescaped.as_str().replace('\r', "") - && trees.next().is_none() - } - _ => false, + } => Lit::from_lit_token(*lit, DUMMY_SP), + Token { + kind: TokenKind::Interpolated(nonterminal), + span: _, + } => match nonterminal.as_ref() { + Nonterminal::NtExpr(expr) => match &expr.kind { + ExprKind::Lit(lit) => Ok(lit.clone()), + _ => return false, + }, + _ => return false, }, + _ => return false, + } { + Ok(Lit { + token: + token::Lit { + kind: token::LitKind::Str, + symbol: _, + suffix: None, + }, + kind: LitKind::Str(symbol, StrStyle::Cooked), + span: _, + }) => symbol.as_str().replace('\r', "") == unescaped.as_str().replace('\r', ""), _ => false, } } @@ -587,9 +612,7 @@ impl SpanlessEq for AttrKind { SpanlessEq::eq(&path, &item2.path) && match &item2.args { MacArgs::Empty | MacArgs::Delimited(..) => false, - MacArgs::Eq(_span, tokens) => { - is_escaped_literal(tokens.trees_ref(), *unescaped) - } + MacArgs::Eq(_span, token) => is_escaped_literal(token, *unescaped), } } (AttrKind::Normal(..), AttrKind::DocComment(..)) => SpanlessEq::eq(other, self), diff --git a/tests/debug/gen.rs b/tests/debug/gen.rs index 85a1a39079..1eae327bb1 100644 --- a/tests/debug/gen.rs +++ b/tests/debug/gen.rs @@ -3424,12 +3424,24 @@ impl Debug for Lite { fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { let _val = &self.value; match _val { - syn::Lit::Str(_val) => write!(formatter, "{:?}", _val.value()), - syn::Lit::ByteStr(_val) => write!(formatter, "{:?}", _val.value()), - syn::Lit::Byte(_val) => write!(formatter, "{:?}", _val.value()), - syn::Lit::Char(_val) => write!(formatter, "{:?}", _val.value()), - syn::Lit::Int(_val) => write!(formatter, "{}", _val), - syn::Lit::Float(_val) => write!(formatter, "{}", _val), + syn::Lit::Str(_val) => { + write!(formatter, "{:?}", _val.value()) + } + syn::Lit::ByteStr(_val) => { + write!(formatter, "{:?}", _val.value()) + } + syn::Lit::Byte(_val) => { + write!(formatter, "{:?}", _val.value()) + } + syn::Lit::Char(_val) => { + write!(formatter, "{:?}", _val.value()) + } + syn::Lit::Int(_val) => { + write!(formatter, "{}", _val) + } + syn::Lit::Float(_val) => { + write!(formatter, "{}", _val) + } syn::Lit::Bool(_val) => { let mut formatter = formatter.debug_struct("Lit::Bool"); formatter.field("value", Lite(&_val.value)); diff --git a/tests/repo/mod.rs b/tests/repo/mod.rs index afb712731d..1aec125535 100644 --- a/tests/repo/mod.rs +++ b/tests/repo/mod.rs @@ -8,7 +8,7 @@ use std::path::Path; use tar::Archive; use walkdir::DirEntry; -const REVISION: &str = "72da5a9d85a522b11e80d0fdd1fd95247d442604"; +const REVISION: &str = "e708cbd91c9cae4426d69270248362b423324556"; #[rustfmt::skip] static EXCLUDE: &[&str] = &[ @@ -27,11 +27,11 @@ static EXCLUDE: &[&str] = &[ "src/test/rustdoc-ui/test-compile-fail3.rs", "src/test/ui/include-single-expr-helper.rs", "src/test/ui/include-single-expr-helper-1.rs", - "src/test/ui/issues/auxiliary/issue-21146-inc.rs", "src/test/ui/json-bom-plus-crlf-multifile-aux.rs", "src/test/ui/lint/expansion-time-include.rs", "src/test/ui/macros/auxiliary/macro-comma-support.rs", "src/test/ui/macros/auxiliary/macro-include-items-expr.rs", + "src/test/ui/parser/auxiliary/issue-21146-inc.rs", ]; pub fn base_dir_filter(entry: &DirEntry) -> bool { diff --git a/tests/test_item.rs b/tests/test_item.rs index a28c8f33b4..7695f19906 100644 --- a/tests/test_item.rs +++ b/tests/test_item.rs @@ -4,7 +4,7 @@ mod macros; use proc_macro2::{Delimiter, Group, Ident, Span, TokenStream, TokenTree}; use quote::quote; use std::iter::FromIterator; -use syn::Item; +use syn::{Item, ItemTrait}; #[test] fn test_macro_variable_attr() { @@ -118,3 +118,126 @@ fn test_negative_impl() { } "###); } + +#[test] +fn test_macro_variable_impl() { + // mimics the token stream corresponding to `impl $trait for $ty {}` + let tokens = TokenStream::from_iter(vec![ + TokenTree::Ident(Ident::new("impl", Span::call_site())), + TokenTree::Group(Group::new(Delimiter::None, quote!(Trait))), + TokenTree::Ident(Ident::new("for", Span::call_site())), + TokenTree::Group(Group::new(Delimiter::None, quote!(Type))), + TokenTree::Group(Group::new(Delimiter::Brace, TokenStream::new())), + ]); + + snapshot!(tokens as Item, @r###" + Item::Impl { + generics: Generics, + trait_: Some(( + None, + Path { + segments: [ + PathSegment { + ident: "Trait", + arguments: None, + }, + ], + }, + )), + self_ty: Type::Group { + elem: Type::Path { + path: Path { + segments: [ + PathSegment { + ident: "Type", + arguments: None, + }, + ], + }, + }, + }, + } + "###); +} + +#[test] +fn test_supertraits() { + // Rustc parses all of the following. + + #[rustfmt::skip] + let tokens = quote!(trait Trait where {}); + snapshot!(tokens as ItemTrait, @r###" + ItemTrait { + vis: Inherited, + ident: "Trait", + generics: Generics { + where_clause: Some(WhereClause), + }, + } + "###); + + #[rustfmt::skip] + let tokens = quote!(trait Trait: where {}); + snapshot!(tokens as ItemTrait, @r###" + ItemTrait { + vis: Inherited, + ident: "Trait", + generics: Generics { + where_clause: Some(WhereClause), + }, + colon_token: Some, + } + "###); + + #[rustfmt::skip] + let tokens = quote!(trait Trait: Sized where {}); + snapshot!(tokens as ItemTrait, @r###" + ItemTrait { + vis: Inherited, + ident: "Trait", + generics: Generics { + where_clause: Some(WhereClause), + }, + colon_token: Some, + supertraits: [ + Trait(TraitBound { + modifier: None, + path: Path { + segments: [ + PathSegment { + ident: "Sized", + arguments: None, + }, + ], + }, + }), + ], + } + "###); + + #[rustfmt::skip] + let tokens = quote!(trait Trait: Sized + where {}); + snapshot!(tokens as ItemTrait, @r###" + ItemTrait { + vis: Inherited, + ident: "Trait", + generics: Generics { + where_clause: Some(WhereClause), + }, + colon_token: Some, + supertraits: [ + Trait(TraitBound { + modifier: None, + path: Path { + segments: [ + PathSegment { + ident: "Sized", + arguments: None, + }, + ], + }, + }), + ], + } + "###); +} diff --git a/tests/test_precedence.rs b/tests/test_precedence.rs index f8027b19c4..4b4cb2fb9d 100644 --- a/tests/test_precedence.rs +++ b/tests/test_precedence.rs @@ -205,7 +205,7 @@ fn librustc_brackets(mut librustc_expr: P) -> Option> { struct BracketsVisitor { failed: bool, - }; + } fn flat_map_field(mut f: Field, vis: &mut T) -> Vec { if f.is_shorthand { diff --git a/tests/test_ty.rs b/tests/test_ty.rs index 9cbdcd6b99..7e20dd9936 100644 --- a/tests/test_ty.rs +++ b/tests/test_ty.rs @@ -50,4 +50,170 @@ fn test_macro_variable_type() { }, } "###); + + // mimics the token stream corresponding to `$ty::` + let tokens = TokenStream::from_iter(vec![ + TokenTree::Group(Group::new(Delimiter::None, quote! { ty })), + TokenTree::Punct(Punct::new(':', Spacing::Joint)), + TokenTree::Punct(Punct::new(':', Spacing::Alone)), + TokenTree::Punct(Punct::new('<', Spacing::Alone)), + TokenTree::Ident(Ident::new("T", Span::call_site())), + TokenTree::Punct(Punct::new('>', Spacing::Alone)), + ]); + + snapshot!(tokens as Type, @r###" + Type::Path { + path: Path { + segments: [ + PathSegment { + ident: "ty", + arguments: PathArguments::AngleBracketed { + colon2_token: Some, + args: [ + Type(Type::Path { + path: Path { + segments: [ + PathSegment { + ident: "T", + arguments: None, + }, + ], + }, + }), + ], + }, + }, + ], + }, + } + "###); +} + +#[test] +fn test_group_angle_brackets() { + // mimics the token stream corresponding to `Option<$ty>` + let tokens = TokenStream::from_iter(vec![ + TokenTree::Ident(Ident::new("Option", Span::call_site())), + TokenTree::Punct(Punct::new('<', Spacing::Alone)), + TokenTree::Group(Group::new(Delimiter::None, quote! { Vec })), + TokenTree::Punct(Punct::new('>', Spacing::Alone)), + ]); + + snapshot!(tokens as Type, @r###" + Type::Path { + path: Path { + segments: [ + PathSegment { + ident: "Option", + arguments: PathArguments::AngleBracketed { + args: [ + Type(Type::Group { + elem: Type::Path { + path: Path { + segments: [ + PathSegment { + ident: "Vec", + arguments: PathArguments::AngleBracketed { + args: [ + Type(Type::Path { + path: Path { + segments: [ + PathSegment { + ident: "u8", + arguments: None, + }, + ], + }, + }), + ], + }, + }, + ], + }, + }, + }), + ], + }, + }, + ], + }, + } + "###); +} + +#[test] +fn test_group_colons() { + // mimics the token stream corresponding to `$ty::Item` + let tokens = TokenStream::from_iter(vec![ + TokenTree::Group(Group::new(Delimiter::None, quote! { Vec })), + TokenTree::Punct(Punct::new(':', Spacing::Joint)), + TokenTree::Punct(Punct::new(':', Spacing::Alone)), + TokenTree::Ident(Ident::new("Item", Span::call_site())), + ]); + + snapshot!(tokens as Type, @r###" + Type::Path { + path: Path { + segments: [ + PathSegment { + ident: "Vec", + arguments: PathArguments::AngleBracketed { + args: [ + Type(Type::Path { + path: Path { + segments: [ + PathSegment { + ident: "u8", + arguments: None, + }, + ], + }, + }), + ], + }, + }, + PathSegment { + ident: "Item", + arguments: None, + }, + ], + }, + } + "###); + + let tokens = TokenStream::from_iter(vec![ + TokenTree::Group(Group::new(Delimiter::None, quote! { [T] })), + TokenTree::Punct(Punct::new(':', Spacing::Joint)), + TokenTree::Punct(Punct::new(':', Spacing::Alone)), + TokenTree::Ident(Ident::new("Element", Span::call_site())), + ]); + + snapshot!(tokens as Type, @r###" + Type::Path { + qself: Some(QSelf { + ty: Type::Slice { + elem: Type::Path { + path: Path { + segments: [ + PathSegment { + ident: "T", + arguments: None, + }, + ], + }, + }, + }, + position: 0, + }), + path: Path { + leading_colon: Some, + segments: [ + PathSegment { + ident: "Element", + arguments: None, + }, + ], + }, + } + "###); } diff --git a/tests/test_visibility.rs b/tests/test_visibility.rs index c3d0ac7a5b..7b2c00ba34 100644 --- a/tests/test_visibility.rs +++ b/tests/test_visibility.rs @@ -92,7 +92,10 @@ fn test_missing_in_path() { #[test] fn test_crate_path() { - assert_vis_parse!("pub(crate::A, crate::B)", Ok(Visibility::Public(_)) + "(crate::A, crate::B)"); + assert_vis_parse!( + "pub(crate::A, crate::B)", + Ok(Visibility::Public(_)) + "(crate::A, crate::B)" + ); } #[test]