about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMatthias Krüger <matthias.krueger@famsik.de>2024-06-28 08:34:09 +0200
committerGitHub <noreply@github.com>2024-06-28 08:34:09 +0200
commitd730f27fc8ecaa0bb17d5f29a6bfc1f42f0ff552 (patch)
treed0755c2609e2e721de6ef92b069cf785fe37da2f
parent2c228260dcf76238de2ac4427b6decd1c74a181b (diff)
parent9387b0bad9b094268675b6fab19afdbe32c7fe51 (diff)
downloadrust-d730f27fc8ecaa0bb17d5f29a6bfc1f42f0ff552.tar.gz
rust-d730f27fc8ecaa0bb17d5f29a6bfc1f42f0ff552.zip
Rollup merge of #127022 - adwinwhite:attrs, r=celinval
Support fetching `Attribute` of items.

Fixes [https://github.com/rust-lang/project-stable-mir/issues/83](https://github.com/rust-lang/project-stable-mir/issues/83)

`rustc_ast::ast::Attribute` doesn't impl `Hash` and `Eq`. Thus it cannot be directly used as key of `IndexMap` in `rustc_smir::rustc_smir::Tables` and we cannot define stable `Attribute` as index to `rustc_ast::ast::Attribute` like `Span` and many other stable definitions.

Since an string (or tokens) and its span contain all info about an attribute, I defined a simple `Attribute` struct on stable side.

I choose to fetch attributes via `tcx::get_attrs_by_path()` due to `get_attrs()` is marked as deprecated and `get_attrs_by_name()` cannot handle name of multiple segments like `rustfmt::skip`.

r? `@celinval`
-rw-r--r--Cargo.lock2
-rw-r--r--compiler/rustc_smir/Cargo.toml2
-rw-r--r--compiler/rustc_smir/src/rustc_smir/context.rs40
-rw-r--r--compiler/stable_mir/src/compiler_interface.rs10
-rw-r--r--compiler/stable_mir/src/crate_def.rs37
-rw-r--r--tests/ui-fulldeps/stable-mir/check_attribute.rs155
6 files changed, 246 insertions, 0 deletions
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 @@ impl<'tcx> Context for TablesWrapper<'tcx> {
         }
     }
 
+    fn get_attrs_by_path(
+        &self,
+        def_id: stable_mir::DefId,
+        attr: &[stable_mir::Symbol],
+    ) -> Vec<stable_mir::crate_def::Attribute> {
+        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<stable_mir::crate_def::Attribute> {
+        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<Attribute>;
+
+    /// Get all attributes of a definition.
+    fn get_all_attrs(&self, def_id: DefId) -> Vec<Attribute>;
+
     /// 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 @@ pub trait CrateDef {
         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<Attribute> {
+        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<Attribute> {
+        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 @@ pub trait CrateDefType: CrateDef {
     }
 }
 
+#[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, "<Foo as std::fmt::Debug>::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(())
+}