diff --git a/Cargo.lock b/Cargo.lock index e977964b72c..0182eca0505 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4675,6 +4675,8 @@ name = "rustc_smir" version = "0.0.0" dependencies = [ "rustc_abi", + "rustc_ast", + "rustc_ast_pretty", "rustc_data_structures", "rustc_hir", "rustc_middle", diff --git a/compiler/rustc_smir/Cargo.toml b/compiler/rustc_smir/Cargo.toml index 1e0a60bc371..1230667ee91 100644 --- a/compiler/rustc_smir/Cargo.toml +++ b/compiler/rustc_smir/Cargo.toml @@ -6,6 +6,8 @@ edition = "2021" [dependencies] # tidy-alphabetical-start rustc_abi = { path = "../rustc_abi" } +rustc_ast = { path = "../rustc_ast" } +rustc_ast_pretty = { path = "../rustc_ast_pretty" } rustc_data_structures = { path = "../rustc_data_structures" } rustc_hir = { path = "../rustc_hir" } rustc_middle = { path = "../rustc_middle" } diff --git a/compiler/rustc_smir/src/rustc_smir/context.rs b/compiler/rustc_smir/src/rustc_smir/context.rs index dde5e30c3d0..e23f4289e98 100644 --- a/compiler/rustc_smir/src/rustc_smir/context.rs +++ b/compiler/rustc_smir/src/rustc_smir/context.rs @@ -228,6 +228,46 @@ fn def_name(&self, def_id: stable_mir::DefId, trimmed: bool) -> Symbol { } } + fn get_attrs_by_path( + &self, + def_id: stable_mir::DefId, + attr: &[stable_mir::Symbol], + ) -> Vec { + let mut tables = self.0.borrow_mut(); + let tcx = tables.tcx; + let did = tables[def_id]; + let attr_name: Vec<_> = + attr.iter().map(|seg| rustc_span::symbol::Symbol::intern(&seg)).collect(); + tcx.get_attrs_by_path(did, &attr_name) + .map(|attribute| { + let attr_str = rustc_ast_pretty::pprust::attribute_to_string(attribute); + let span = attribute.span; + stable_mir::crate_def::Attribute::new(attr_str, span.stable(&mut *tables)) + }) + .collect() + } + + fn get_all_attrs(&self, def_id: stable_mir::DefId) -> Vec { + let mut tables = self.0.borrow_mut(); + let tcx = tables.tcx; + let did = tables[def_id]; + let filter_fn = move |a: &&rustc_ast::ast::Attribute| { + matches!(a.kind, rustc_ast::ast::AttrKind::Normal(_)) + }; + let attrs_iter = if let Some(did) = did.as_local() { + tcx.hir().attrs(tcx.local_def_id_to_hir_id(did)).iter().filter(filter_fn) + } else { + tcx.item_attrs(did).iter().filter(filter_fn) + }; + attrs_iter + .map(|attribute| { + let attr_str = rustc_ast_pretty::pprust::attribute_to_string(attribute); + let span = attribute.span; + stable_mir::crate_def::Attribute::new(attr_str, span.stable(&mut *tables)) + }) + .collect() + } + fn span_to_string(&self, span: stable_mir::ty::Span) -> String { let tables = self.0.borrow(); tables.tcx.sess.source_map().span_to_diagnostic_string(tables[span]) diff --git a/compiler/stable_mir/src/compiler_interface.rs b/compiler/stable_mir/src/compiler_interface.rs index 44dbf549c1a..5f2d9b96c73 100644 --- a/compiler/stable_mir/src/compiler_interface.rs +++ b/compiler/stable_mir/src/compiler_interface.rs @@ -6,6 +6,7 @@ use std::cell::Cell; use crate::abi::{FnAbi, Layout, LayoutShape}; +use crate::crate_def::Attribute; use crate::mir::alloc::{AllocId, GlobalAlloc}; use crate::mir::mono::{Instance, InstanceDef, StaticDef}; use crate::mir::{BinOp, Body, Place, UnOp}; @@ -55,6 +56,15 @@ pub trait Context { /// Returns the name of given `DefId` fn def_name(&self, def_id: DefId, trimmed: bool) -> Symbol; + /// Return attributes with the given attribute name. + /// + /// Single segmented name like `#[inline]` is specified as `&["inline".to_string()]`. + /// Multi-segmented name like `#[rustfmt::skip]` is specified as `&["rustfmt".to_string(), "skip".to_string()]`. + fn get_attrs_by_path(&self, def_id: DefId, attr: &[Symbol]) -> Vec; + + /// Get all attributes of a definition. + fn get_all_attrs(&self, def_id: DefId) -> Vec; + /// Returns printable, human readable form of `Span` fn span_to_string(&self, span: Span) -> String; diff --git a/compiler/stable_mir/src/crate_def.rs b/compiler/stable_mir/src/crate_def.rs index 67752a5e629..d9b987c28a2 100644 --- a/compiler/stable_mir/src/crate_def.rs +++ b/compiler/stable_mir/src/crate_def.rs @@ -50,6 +50,21 @@ fn span(&self) -> Span { let def_id = self.def_id(); with(|cx| cx.span_of_an_item(def_id)) } + + /// Return attributes with the given attribute name. + /// + /// Single segmented name like `#[inline]` is specified as `&["inline".to_string()]`. + /// Multi-segmented name like `#[rustfmt::skip]` is specified as `&["rustfmt".to_string(), "skip".to_string()]`. + fn attrs_by_path(&self, attr: &[Symbol]) -> Vec { + let def_id = self.def_id(); + with(|cx| cx.get_attrs_by_path(def_id, attr)) + } + + /// Return all attributes of this definition. + fn all_attrs(&self) -> Vec { + let def_id = self.def_id(); + with(|cx| cx.get_all_attrs(def_id)) + } } /// A trait that can be used to retrieve a definition's type. @@ -69,6 +84,28 @@ fn ty_with_args(&self, args: &GenericArgs) -> Ty { } } +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Attribute { + value: String, + span: Span, +} + +impl Attribute { + pub fn new(value: String, span: Span) -> Attribute { + Attribute { value, span } + } + + /// Get the span of this attribute. + pub fn span(&self) -> Span { + self.span + } + + /// Get the string representation of this attribute. + pub fn as_str(&self) -> &str { + &self.value + } +} + macro_rules! crate_def { ( $(#[$attr:meta])* $vis:vis $name:ident $(;)? diff --git a/tests/ui-fulldeps/stable-mir/check_attribute.rs b/tests/ui-fulldeps/stable-mir/check_attribute.rs new file mode 100644 index 00000000000..be52853a479 --- /dev/null +++ b/tests/ui-fulldeps/stable-mir/check_attribute.rs @@ -0,0 +1,155 @@ +//@ run-pass +//! Test information regarding type layout. + +//@ ignore-stage1 +//@ ignore-cross-compile +//@ ignore-remote +//@ ignore-windows-gnu mingw has troubles with linking https://github.com/rust-lang/rust/pull/116837 + +#![feature(rustc_private)] +#![feature(control_flow_enum)] + +extern crate rustc_hir; +#[macro_use] +extern crate rustc_smir; +extern crate rustc_driver; +extern crate rustc_interface; +extern crate stable_mir; + +use rustc_smir::rustc_internal; +use stable_mir::{CrateDef, CrateItems}; +use std::io::Write; +use std::ops::ControlFlow; + +const CRATE_NAME: &str = "input"; + +/// This function uses the Stable MIR APIs to get information about the test crate. +fn test_stable_mir() -> ControlFlow<()> { + // Find items in the local crate. + let items = stable_mir::all_local_items(); + + test_builtins(&items); + test_derive(&items); + test_tool(&items); + test_all_attrs(&items); + + ControlFlow::Continue(()) +} + +// Test built-in attributes. +fn test_builtins(items: &CrateItems) { + let target_fn = *get_item(&items, "builtins_fn").unwrap(); + let allow_attrs = target_fn.attrs_by_path(&["allow".to_string()]); + assert_eq!(allow_attrs[0].as_str(), "#![allow(unused_variables)]"); + + let inline_attrs = target_fn.attrs_by_path(&["inline".to_string()]); + assert_eq!(inline_attrs[0].as_str(), "#[inline]"); + + let deprecated_attrs = target_fn.attrs_by_path(&["deprecated".to_string()]); + assert_eq!(deprecated_attrs[0].as_str(), "#[deprecated(since = \"5.2.0\")]"); +} + +// Test derive attribute. +fn test_derive(items: &CrateItems) { + let target_struct = *get_item(&items, "Foo").unwrap(); + let attrs = target_struct.attrs_by_path(&["derive".to_string()]); + // No `derive` attribute since it's expanded before MIR. + assert_eq!(attrs.len(), 0); + + // Check derived trait method's attributes. + let derived_fmt = *get_item(&items, "::fmt").unwrap(); + // The Rust reference lies about this attribute. It doesn't show up in `clone` or `fmt` impl. + let _fmt_attrs = derived_fmt.attrs_by_path(&["automatically_derived".to_string()]); +} + +// Test tool attributes. +fn test_tool(items: &CrateItems) { + let rustfmt_fn = *get_item(&items, "do_not_format").unwrap(); + let rustfmt_attrs = rustfmt_fn.attrs_by_path(&["rustfmt".to_string(), "skip".to_string()]); + assert_eq!(rustfmt_attrs[0].as_str(), "#[rustfmt::skip]"); + + let clippy_fn = *get_item(&items, "complex_fn").unwrap(); + let clippy_attrs = clippy_fn.attrs_by_path(&["clippy".to_string(), + "cyclomatic_complexity".to_string()]); + assert_eq!(clippy_attrs[0].as_str(), "#[clippy::cyclomatic_complexity = \"100\"]"); +} + +fn test_all_attrs(items: &CrateItems) { + let target_fn = *get_item(&items, "many_attrs").unwrap(); + let all_attrs = target_fn.all_attrs(); + assert_eq!(all_attrs[0].as_str(), "#[inline]"); + assert_eq!(all_attrs[1].as_str(), "#[allow(unused_variables)]"); + assert_eq!(all_attrs[2].as_str(), "#[allow(dead_code)]"); + assert_eq!(all_attrs[3].as_str(), "#[allow(unused_imports)]"); + assert_eq!(all_attrs[4].as_str(), "#![allow(clippy::filter_map)]"); +} + + +fn get_item<'a>( + items: &'a CrateItems, + name: &str, +) -> Option<&'a stable_mir::CrateItem> { + items.iter().find(|crate_item| crate_item.name() == name) +} + +/// This test will generate and analyze a dummy crate using the stable mir. +/// For that, it will first write the dummy crate into a file. +/// Then it will create a `StableMir` using custom arguments and then +/// it will run the compiler. +fn main() { + let path = "attribute_input.rs"; + generate_input(&path).unwrap(); + let args = vec![ + "rustc".to_string(), + "--crate-type=lib".to_string(), + "--crate-name".to_string(), + CRATE_NAME.to_string(), + path.to_string(), + ]; + run!(args, test_stable_mir).unwrap(); +} + +fn generate_input(path: &str) -> std::io::Result<()> { + let mut file = std::fs::File::create(path)?; + write!( + file, + r#" + // General metadata applied to the enclosing module or crate. + #![crate_type = "lib"] + + // Mixed inner and outer attributes. + #[inline] + #[deprecated(since = "5.2.0")] + fn builtins_fn() {{ + #![allow(unused_variables)] + + let x = (); + let y = (); + let z = (); + }} + + // A derive attribute to automatically implement a trait. + #[derive(Debug, Clone, Copy)] + struct Foo(u32); + + // A rustfmt tool attribute. + #[rustfmt::skip] + fn do_not_format() {{}} + + // A clippy tool attribute. + #[clippy::cyclomatic_complexity = "100"] + pub fn complex_fn() {{}} + + // A function with many attributes. + #[inline] + #[allow(unused_variables)] + #[allow(dead_code)] + #[allow(unused_imports)] + fn many_attrs() {{ + #![allow(clippy::filter_map)] + todo!() + }} + "# + )?; + Ok(()) +}