Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle imports & re-exports of all kinds of macro items. (#666) #669

Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions src/adapter/mod.rs
Original file line number Diff line number Diff line change
@@ -229,8 +229,22 @@ impl<'a> Adapter<'a> for &'a RustdocAdapter<'a> {
match type_name.as_ref() {
"CrateDiff" => edges::resolve_crate_diff_edge(contexts, edge_name),
"Crate" => edges::resolve_crate_edge(self, contexts, edge_name, resolve_info),
"Importable" | "ImplOwner" | "Struct" | "Enum" | "Union" | "Trait" | "Function"
| "GlobalValue" | "Constant" | "Static" | "Module"
"Importable"
| "ImplOwner"
| "Struct"
| "Enum"
| "Union"
| "Trait"
| "Function"
| "GlobalValue"
| "Constant"
| "Static"
| "Module"
| "Macro"
| "ProcMacro"
| "FunctionLikeProcMacro"
| "AttributeProcMacro"
| "DeriveProcMacro"
if matches!(edge_name.as_ref(), "importable_path" | "canonical_path") =>
{
edges::resolve_importable_edge(
17 changes: 17 additions & 0 deletions src/adapter/tests.rs
Original file line number Diff line number Diff line change
@@ -2333,6 +2333,11 @@ fn proc_macros() {
name @output
public_api_eligible @output
visibility_limit @output

importable_path {
path @output
public_api @output
}
}
}
}
@@ -2350,6 +2355,8 @@ fn proc_macros() {
name: String,
public_api_eligible: bool,
visibility_limit: String,
path: Vec<String>,
public_api: bool,
}

let mut results: Vec<_> =
@@ -2369,30 +2376,40 @@ fn proc_macros() {
name: "make_answer".into(),
public_api_eligible: true,
visibility_limit: "public".into(),
path: vec!["proc_macros".into(), "make_answer".into()],
public_api: true,
},
Output {
kind: "AttributeProcMacro".into(),
name: "return_as_is".into(),
public_api_eligible: true,
visibility_limit: "public".into(),
path: vec!["proc_macros".into(), "return_as_is".into()],
public_api: true,
},
Output {
kind: "DeriveProcMacro".into(),
name: "AnswerFn".into(),
public_api_eligible: true,
visibility_limit: "public".into(),
path: vec!["proc_macros".into(), "AnswerFn".into()],
public_api: true,
},
Output {
kind: "DeriveProcMacro".into(),
name: "HelperAttr".into(),
public_api_eligible: true,
visibility_limit: "public".into(),
path: vec!["proc_macros".into(), "HelperAttr".into()],
public_api: true,
},
Output {
kind: "FunctionLikeProcMacro".into(),
name: "hidden".into(),
public_api_eligible: false,
visibility_limit: "public".into(),
path: vec!["proc_macros".into(), "hidden".into()],
public_api: false,
},
];
expected_results.sort_unstable();
29 changes: 29 additions & 0 deletions src/indexed_crate.rs
Original file line number Diff line number Diff line change
@@ -2373,5 +2373,34 @@ expected exactly one importable path for `Foo` items in this crate but got: {act
}
}
}

#[test]
fn reexport_declarative_macro() {
let test_crate = "reexport_declarative_macro";
let expected_items = btreemap! {
"top_level_exported" => btreeset![
"reexport_declarative_macro::top_level_exported",
],
"private_mod_exported" => btreeset![
"reexport_declarative_macro::private_mod_exported",
],
"top_level_reexported" => btreeset![
"reexport_declarative_macro::top_level_reexported",
"reexport_declarative_macro::macros::top_level_reexported",
"reexport_declarative_macro::reexports::top_level_reexported",
"reexport_declarative_macro::glob_reexports::top_level_reexported",
],
"private_mod_reexported" => btreeset![
"reexport_declarative_macro::private_mod_reexported",
"reexport_declarative_macro::macros::private_mod_reexported",
"reexport_declarative_macro::reexports::private_mod_reexported",
"reexport_declarative_macro::glob_reexports::private_mod_reexported",
],
"top_level_not_exported" => btreeset![],
"private_mod_not_exported" => btreeset![],
};

assert_exported_items_match(test_crate, &expected_items);
}
}
}
38 changes: 29 additions & 9 deletions src/rustdoc_schema.graphql
Original file line number Diff line number Diff line change
@@ -1983,15 +1983,15 @@ type AssociatedConstant implements Item {
"""
A `macro_rules` declarative macro.

When such macros are exported by a crate, they are always exported at the top level of the crate,
When declarative macros are exported by a crate, they are always exported at the top level of the crate,
even if the macro's definition is located inside a `mod`. In essence, the `#[macro_export]`
attribute behaves as a `pub use` of the macro into the crate's root module.

This is why we don't model macro items as `Importable`:
- They always have only one importable path: `the_crate::macro_name`.
- Their names are in their own namespace, and do not mix with the types or values namespaces.
However, declarative macros *can* be re-exported by other modules as well.
Those other modules just have to re-export the `#[macro_export]`-ed item from the crate root,
instead of the item at its original declaration location.
"""
type Macro implements Item {
type Macro implements Item & Importable {
# properties from Item
id: String!
crate_id: Int!
@@ -2035,6 +2035,10 @@ type Macro implements Item {
# edges from Item
attribute: [Attribute!]
span: Span

# edges from Importable
importable_path: [ImportablePath!]
canonical_path: Path
}

"""
@@ -2049,7 +2053,7 @@ This is why we don't model macro items as `Importable`:
- They always have only one importable path: `the_crate::macro_name`.
- Their names are in their own namespace, and do not mix with the types or values namespaces.
"""
interface ProcMacro implements Item {
interface ProcMacro implements Item & Importable {
# properties from Item
id: String!
crate_id: Int!
@@ -2093,6 +2097,10 @@ interface ProcMacro implements Item {
# edges from Item
attribute: [Attribute!]
span: Span

# edges from Importable
importable_path: [ImportablePath!]
canonical_path: Path
}

"""
@@ -2107,7 +2115,7 @@ This is why we don't model macro items as `Importable`:
- They always have only one importable path: `the_crate::macro_name`.
- Their names are in their own namespace, and do not mix with the types or values namespaces.
"""
type FunctionLikeProcMacro implements Item & ProcMacro {
type FunctionLikeProcMacro implements Item & Importable & ProcMacro {
# properties from Item
id: String!
crate_id: Int!
@@ -2151,6 +2159,10 @@ type FunctionLikeProcMacro implements Item & ProcMacro {
# edges from Item
attribute: [Attribute!]
span: Span

# edges from Importable
importable_path: [ImportablePath!]
canonical_path: Path
}

"""
@@ -2164,7 +2176,7 @@ This is why we don't model macro items as `Importable`:
- They always have only one importable path: `the_crate::macro_name`.
- Their names are in their own namespace, and do not mix with the types or values namespaces.
"""
type AttributeProcMacro implements Item & ProcMacro {
type AttributeProcMacro implements Item & Importable & ProcMacro {
# properties from Item
id: String!
crate_id: Int!
@@ -2208,6 +2220,10 @@ type AttributeProcMacro implements Item & ProcMacro {
# edges from Item
attribute: [Attribute!]
span: Span

# edges from Importable
importable_path: [ImportablePath!]
canonical_path: Path
}

"""
@@ -2221,7 +2237,7 @@ This is why we don't model macro items as `Importable`:
- They always have only one importable path: `the_crate::MacroName`.
- Their names are in their own namespace, and do not mix with the types or values namespaces.
"""
type DeriveProcMacro implements Item & ProcMacro {
type DeriveProcMacro implements Item & Importable & ProcMacro {
# properties from Item
id: String!
crate_id: Int!
@@ -2266,6 +2282,10 @@ type DeriveProcMacro implements Item & ProcMacro {
attribute: [Attribute!]
span: Span

# edges from Importable
importable_path: [ImportablePath!]
canonical_path: Path

# own edges
"""
Any additional helper attributes defined by this macro.
41 changes: 41 additions & 0 deletions src/visibility_tracker.rs
Original file line number Diff line number Diff line change
@@ -228,13 +228,18 @@ impl<'a> VisibilityTracker<'a> {
enum NamespacedName<'a> {
Values(&'a str),
Types(&'a str),
// https://doc.rust-lang.org/reference/names/namespaces.html#sub-namespaces
BangMacros(&'a str),
AttrOrDeriveMacros(&'a str),
}

impl<'a> NamespacedName<'a> {
fn rename(&self, new_name: &'a str) -> Self {
match self {
NamespacedName::Values(_) => NamespacedName::Values(new_name),
NamespacedName::Types(_) => NamespacedName::Types(new_name),
NamespacedName::BangMacros(_) => NamespacedName::BangMacros(new_name),
NamespacedName::AttrOrDeriveMacros(_) => NamespacedName::AttrOrDeriveMacros(new_name),
}
}
}
@@ -391,6 +396,21 @@ fn get_names_for_item<'a>(
.into_iter()
.flatten()
}
ItemEnum::Macro(..) => {
let item_name = item.name.as_deref().expect("item did not have a name");
[Some(NamespacedName::BangMacros(item_name)), None]
.into_iter()
.flatten()
}
ItemEnum::ProcMacro(m) => {
let item_name = item.name.as_deref().expect("item did not have a name");
let namespaced_name = if m.kind == rustdoc_types::MacroKind::Bang {
NamespacedName::BangMacros(item_name)
} else {
NamespacedName::AttrOrDeriveMacros(item_name)
};
[Some(namespaced_name), None].into_iter().flatten()
}
_ => [None, None].into_iter().flatten(),
}
}
@@ -469,6 +489,27 @@ fn resolve_crate_names(crate_: &Crate) -> NameResolution<'_> {
);
}
}
} else if let ItemEnum::Macro(..) = &inner_item.inner {
// `#[macro_export]` moves declarative macros to the crate root,
// regardless of what module they were declared in. Such macros are always public.
// Without `#[macro_export]`, declarative macros have no path-based scope:
// https://doc.rust-lang.org/reference/macros-by-example.html#path-based-scope
if inner_item
.attrs
.iter()
.any(|attr| attr.as_str() == "#[macro_export]")
{
for name in get_names_for_item(crate_, inner_item) {
result
.names_defined_in_module
.entry(IdRef::new(&crate_.root))
.or_default()
.insert(
name,
(Definition::new_direct(IdRef::new(&inner_item.id)), true),
);
}
}
} else {
for name in get_names_for_item(crate_, inner_item) {
result
6 changes: 6 additions & 0 deletions test_crates/reexport_declarative_macro/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[package]
name = "reexport_declarative_macro"
version = "0.1.0"
edition = "2021"

[dependencies]
53 changes: 53 additions & 0 deletions test_crates/reexport_declarative_macro/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//! This crate exports the following macros:
//! - `top_level_exported`, at top level only
//! - `private_mod_exported`, at top level only
//! - `top_level_reexported`, at top level, and inside
//! the `macros`, `reexports`, and `glob_reexports` modules
//! - `private_mod_reexported`, at top level, and inside
//! the `macros`, `reexports`, and `glob_reexports` modules
//!
//! The `top_level_not_exported` and `private_mod_not_exported` macros are not exported.
#![allow(unused_macros)]

macro_rules! top_level_not_exported {
() => {}
}

#[macro_export]
macro_rules! top_level_exported {
() => {}
}

#[macro_export]
macro_rules! top_level_reexported {
() => {}
}

mod private {
#[macro_export]
macro_rules! private_mod_exported {
() => {}
}

macro_rules! private_mod_not_exported {
() => {}
}

#[macro_export]
macro_rules! private_mod_reexported {
() => {}
}
}

pub mod macros {
pub use crate::private_mod_reexported;
pub use crate::top_level_reexported;
}

pub mod glob_reexports {
pub use crate::macros::*;
}

pub mod reexports {
pub use crate::macros::{private_mod_reexported, top_level_reexported};
}