Skip to content

Commit

Permalink
Rollup merge of rust-lang#127022 - adwinwhite:attrs, r=celinval
Browse files Browse the repository at this point in the history
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`
  • Loading branch information
matthiaskrgr authored Jun 28, 2024
2 parents 2c22826 + 9387b0b commit d730f27
Show file tree
Hide file tree
Showing 6 changed files with 246 additions and 0 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_smir/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
40 changes: 40 additions & 0 deletions compiler/rustc_smir/src/rustc_smir/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down
10 changes: 10 additions & 0 deletions compiler/stable_mir/src/compiler_interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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;

Expand Down
37 changes: 37 additions & 0 deletions compiler/stable_mir/src/crate_def.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 $(;)?
Expand Down
155 changes: 155 additions & 0 deletions tests/ui-fulldeps/stable-mir/check_attribute.rs
Original file line number Diff line number Diff line change
@@ -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(())
}

0 comments on commit d730f27

Please sign in to comment.