Skip to content

Re-export information is lost [rustdoc's json output] #101059

Open

Description

@LukeMathWalker

Hi!
I have been playing around with rustdoc's JSON output for the past few weeks and I have now stumbled on a road block that I can't easily sort out on my own.

The problem

I have a function signature that looks like this:

pub fn extract_path(
    _inner: pavex_runtime::http::Request<pavex_runtime::hyper::body::Body>,
) -> PathBuf {
    todo!()
}

where pavex_runtime is a third-party dependency that does nothing more than a public re-export:

//! pavex_runtime/src/lib.rs
pub use hyper;
pub use http;

If I look at that function in the JSON output for the relevant crate, I see this item (stripping out irrelevant fields):

{
      "id": "0:18:1598",
      "crate_id": 0,
      "name": "extract_path",
      "kind": "function",
      "inner": {
        "decl": {
          "inputs": [
            [
              "_inner",
              {
                "kind": "resolved_path",
                "inner": {
                  "name": "pavex_runtime::http::Request",
                  "id": "31:1361:1602",
                  }
                }
              }
            ]
          ],
      }

The name in inner hints at the fact that we depend on http::Request through a re-export via pavex_runtime.
If I follow the id, I instead go directly to the canonical item definition in http:

    "31:1361:1602": {
      "crate_id": 31,
      "path": [
        "http",
        "request",
        "Request"
      ],
      "kind": "struct"
    },

name seems to be the only place where rustdoc exposes the information that the dependency from my crate to http flows through a re-export in pavex_runtime.
Further experiments seem to suggest that name is set to whatever is used as the type name alias in the function definition.
E.g. by rewriting the definition as

use pavex_runtime::http::Request;

pub fn extract_path(
    _inner: Request<pavex_runtime::hyper::body::Body>,
) -> PathBuf {
    todo!()
}

the name now shows up as Request.
This would imply that there is no way to reliably detect that extract_path (and the crate where it is defined) depend on http through a re-export in pavex_runtime.

What I expect to see

I'd expect the extract_path function item to refer, in its parameter list, to some kind of re-export item kind that is associated with pavex_runtime instead of going directly to http.
E.g. an item of kind "import".

This would allow consumers of the JSON representation to navigate up to the source type (http::Request) while retaining the information that the current crate does not depend on it directly (via pavex_runtime).

Other relevant information

Latest nightly, format version 18.

Related zulip's discussion with @GuillaumeGomez

Activity

  1. added
    T-rustdocRelevant to the rustdoc team, which will review and decide on the PR/issue.
    on Aug 26, 2022
  2. Enselic commented on Aug 27, 2022

    @Enselic
    Member

    Also see #99287 which is the PR that stopped including re-exported items of external crates to fix a bunch of ICEs

    Also related: #99513

  3. aDotInTheVoid commented on Aug 27, 2022

    @aDotInTheVoid
    Member

    MCVE

    src/lib.rs

    pub fn extract(_req: pavex_runtime::http::Request) {}

    pavex_runtime/src/lib.rs

    pub use http;

    http/src/lib.rs

    pub struct Request;
    Cargo files **Cargo.toml** ```toml [package] name = "rust-101059" version = "0.1.0" edition = "2021" [dependencies] pavex_runtime = { path = "./pavex_runtime" } ``` **pavex_runtime/Cargo.toml** ```toml [package] name = "pavex_runtime" edition = "2021" version = "0.1.0" [dependencies] http = { path = "../http" } ``` **http/Cargo.toml** ```toml [package] name = "http" edition = "2021" version = "0.1.0" ```

    Which produces (abridged)

    {
      "crate_version": "0.1.0",
      "external_crates": {
        "1": {"html_root_url": null, "name": "pavex_runtime"},
        "2": {"html_root_url": null, "name": "http"}
      },
      "format_version": 18,
      "includes_private": false,
      "index": {
        "0:0:1570": {
          "crate_id": 0,
          "id": "0:0:1570",
          "inner": {"is_crate": true, "is_stripped": false, "items": ["0:1:1565"]},
          "kind": "module",
          "name": "rust_101059"
        },
        "0:1:1565": {
          "crate_id": 0,
          "id": "0:1:1565",
          "inner": {
            "decl": {
              "inputs": [
                [
                  "_req",
                  {
                    "inner": {
                      "id": "2:3:1569",
                      "name": "pavex_runtime::http::Request"
                    },
                    "kind": "resolved_path"
                  }
                ]
              ],
              "output": null
            }
          },
          "kind": "function",
          "name": "extract"
        }
      },
      "paths": {
        "0:0:1570": {"crate_id": 0, "kind": "module", "path": ["rust_101059"]},
        "0:1:1565": {"crate_id": 0, "kind": "function", "path": ["rust_101059", "extract"]},
        "1:0:1567": {"crate_id": 1, "kind": "module", "path": ["pavex_runtime"]},
        "2:0:1568": {"crate_id": 2, "kind": "module", "path": ["http"]},
        "2:3:1569": {"crate_id": 2, "kind": "struct", "path": ["http", "Request"]}
      },
      "root": "0:0:1570"
    }

    full

  4. aDotInTheVoid commented on Aug 27, 2022

    @aDotInTheVoid
    Member

    Single crate version:

    #![feature(no_core)]
    #![no_core]
    
    pub mod http {
        pub struct Request;
    }
    pub mod pavex_runtime {
        pub use crate::http;
    }
    pub fn extract(_req: pavex_runtime::http::Request) {}

    Produces

    {
      "crate_version": null,
      "external_crates": {},
      "format_version": 18,
      "includes_private": false,
      "index": {
        "0:0:1570": {
          "crate_id": 0,
          "id": "0:0:1570",
          "inner": {"is_crate": true, "is_stripped": false, "items": ["0:1:1565", "0:4:1567", "0:8:1568"]},
          "kind": "module",
          "name": "single_file"
        },
        "0:1:1565": {
          "crate_id": 0,
          "id": "0:1:1565",
          "inner": {"is_crate": false, "is_stripped": false, "items": ["0:2:1566"]},
          "kind": "module",
          "name": "http"
        },
        "0:2:1566": {
          "crate_id": 0,
          "id": "0:2:1566",
          "inner": {"struct_type": "unit"},
          "kind": "struct",
          "name": "Request"
        },
        "0:4:1567": {
          "crate_id": 0,
          "id": "0:4:1567",
          "inner": {"is_crate": false, "is_stripped": false, "items": ["0:5"]},
          "kind": "module",
          "name": "pavex_runtime"
        },
        "0:5": {
          "crate_id": 0,
          "id": "0:5",
          "inner": {"glob": false, "id": "0:1:1565", "name": "http", "source": "crate::http"},
          "kind": "import",
          "name": null
        },
        "0:8:1568": {
          "crate_id": 0,
          "id": "0:8:1568",
          "inner": {
            "decl": {
              "c_variadic": false,
              "inputs": [
                [
                  "_req",
                  {
                    "inner": {"id": "0:2:1566", "name": "pavex_runtime::http::Request"},
                    "kind": "resolved_path"
                  }
                ]
              ],
              "output": null
            }
          },
          "kind": "function",
          "name": "extract"
        }
      },
      "paths": {
        "0:0:1570": {"crate_id": 0, "kind": "module", "path": ["single_file"]},
        "0:1:1565": {"crate_id": 0, "kind": "module", "path": ["single_file", "http"]},
        "0:2:1566": {"crate_id": 0, "kind": "struct", "path": ["single_file", "http", "Request"]},
        "0:4:1567": {"crate_id": 0, "kind": "module", "path": ["single_file", "pavex_runtime"]},
        "0:8:1568": {"crate_id": 0, "kind": "function", "path": ["single_file", "extract"]}
      },
      "root": "0:0:1570"
    }
    Full ```json { "crate_version": null, "external_crates": {}, "format_version": 18, "includes_private": false, "index": { "0:0:1570": { "attrs": ["#![feature(no_core)]", "#![no_core]"], "crate_id": 0, "deprecation": null, "docs": null, "id": "0:0:1570", "inner": {"is_crate": true, "is_stripped": false, "items": ["0:1:1565", "0:4:1567", "0:8:1568"]}, "kind": "module", "links": {}, "name": "single_file", "span": {"begin": [1, 0], "end": [12, 53], "filename": "single-file.rs"}, "visibility": "public" }, "0:1:1565": { "attrs": [], "crate_id": 0, "deprecation": null, "docs": null, "id": "0:1:1565", "inner": {"is_crate": false, "is_stripped": false, "items": ["0:2:1566"]}, "kind": "module", "links": {}, "name": "http", "span": {"begin": [4, 0], "end": [4, 12], "filename": "single-file.rs"}, "visibility": "public" }, "0:2:1566": { "attrs": [], "crate_id": 0, "deprecation": null, "docs": null, "id": "0:2:1566", "inner": { "fields": [], "fields_stripped": false, "generics": {"params": [], "where_predicates": []}, "impls": [], "struct_type": "unit" }, "kind": "struct", "links": {}, "name": "Request", "span": {"begin": [5, 4], "end": [5, 23], "filename": "single-file.rs"}, "visibility": "public" }, "0:4:1567": { "attrs": [], "crate_id": 0, "deprecation": null, "docs": null, "id": "0:4:1567", "inner": {"is_crate": false, "is_stripped": false, "items": ["0:5"]}, "kind": "module", "links": {}, "name": "pavex_runtime", "span": {"begin": [8, 0], "end": [8, 21], "filename": "single-file.rs"}, "visibility": "public" }, "0:5": { "attrs": [], "crate_id": 0, "deprecation": null, "docs": null, "id": "0:5", "inner": {"glob": false, "id": "0:1:1565", "name": "http", "source": "crate::http"}, "kind": "import", "links": {}, "name": null, "span": {"begin": [9, 4], "end": [9, 24], "filename": "single-file.rs"}, "visibility": "public" }, "0:8:1568": { "attrs": [], "crate_id": 0, "deprecation": null, "docs": null, "id": "0:8:1568", "inner": { "decl": { "c_variadic": false, "inputs": [ [ "_req", { "inner": { "args": {"angle_bracketed": {"args": [], "bindings": []}}, "id": "0:2:1566", "name": "pavex_runtime::http::Request" }, "kind": "resolved_path" } ] ], "output": null }, "generics": {"params": [], "where_predicates": []}, "header": {"abi": "Rust", "async": false, "const": false, "unsafe": false} }, "kind": "function", "links": {}, "name": "extract", "span": {"begin": [12, 0], "end": [12, 53], "filename": "single-file.rs"}, "visibility": "public" } }, "paths": { "0:0:1570": {"crate_id": 0, "kind": "module", "path": ["single_file"]}, "0:1:1565": {"crate_id": 0, "kind": "module", "path": ["single_file", "http"]}, "0:2:1566": {"crate_id": 0, "kind": "struct", "path": ["single_file", "http", "Request"]}, "0:4:1567": {"crate_id": 0, "kind": "module", "path": ["single_file", "pavex_runtime"]}, "0:8:1568": {"crate_id": 0, "kind": "function", "path": ["single_file", "extract"]} }, "root": "0:0:1570" } ```
  5. aDotInTheVoid commented on Aug 27, 2022

    @aDotInTheVoid
    Member

    One of the issues here is that the only item is pavex_runtime is its export of http, but not one for it's export of http::Request

    I'm not sure how to solve this without having a item in pavex_runtime for every item in http that it exports, instead of just one item for exporting http

    However, even when just using the struct, instead of a module, we don't link to the import

    #![feature(no_core)]
    #![no_core]
    
    pub mod http {
        pub struct Request;
    }
    
    pub mod pavex_runtime {
        pub use crate::http::Request;
    }
    
    pub fn extract(_req: pavex_runtime::Request) {}
    {
      "crate_version": null,
      "external_crates": {},
      "format_version": 18,
      "includes_private": false,
      "index": {
        "0:0:1570": {
          "crate_id": 0,
          "id": "0:0:1570",
          "inner": {"is_crate": true, "is_stripped": false, "items": ["0:1:1565", "0:4:1567", "0:8:1568"]},
          "kind": "module",
          "name": "single_file_mod"
        },
        "0:1:1565": {
          "crate_id": 0,
          "id": "0:1:1565",
          "inner": {"is_crate": false, "is_stripped": false, "items": ["0:2:1566"]},
          "kind": "module",
          "name": "http"
        },
        "0:2:1566": {
          "crate_id": 0,
          "id": "0:2:1566",
          "inner": {"struct_type": "unit"},
          "kind": "struct",
          "name": "Request"
        },
        "0:4:1567": {
          "crate_id": 0,
          "id": "0:4:1567",
          "inner": {"is_crate": false, "is_stripped": false, "items": ["0:5"]},
          "kind": "module",
          "name": "pavex_runtime"
        },
        "0:5": {
          "crate_id": 0,
          "id": "0:5",
          "inner": {"glob": false, "id": "0:2:1566", "name": "Request", "source": "crate::http::Request"},
          "kind": "import",
          "name": null
        },
        "0:8:1568": {
          "crate_id": 0,
          "id": "0:8:1568",
          "inner": {
            "decl": {
              "c_variadic": false,
              "inputs": [
                [
                  "_req",
                  {
                    "inner": {"id": "0:2:1566", "name": "pavex_runtime::Request"},
                    "kind": "resolved_path"
                  }
                ]
              ],
              "output": null
            }
          },
          "kind": "function",
          "name": "extract"
        }
      },
      "root": "0:0:1570"
    }
  6. LukeMathWalker commented on Aug 27, 2022

    @LukeMathWalker
    ContributorAuthor

    I'm not sure how to solve this without having a item in pavex_runtime for every item in http that it exports, instead of just one item for exporting http

    To an extent, this is what I'd expect to see - an item for every type that is exposed through the public API of the crate via the re-export.

  7. obi1kenobi commented on Aug 29, 2022

    @obi1kenobi
    Member

    Let's ignore the fact that http is quite a stable crate for a second. Say the re-export is for a fictional crate foo. Also say we're generating rustdoc JSON for a crate bar, which re-exports foo.

    Is there a risk that "list what types are re-exported via foo" in the bar JSON is sensitive to the specific version of foo that was installed at rustdoc JSON generation time?

    If bar doesn't restrict the minor version of foo (the default) then new foo minor versions may cause more things to be accessible via the re-export. That would put the bar rustdoc JSON output somewhere between incorrect and misleading, in my book: bar::foo::Quux could compile fine with bar==1.2.3 but that version's rustdoc JSON would be allowed to not mention bar::foo::Quux while mentioning other re-exported types.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-rustdoc-jsonArea: Rustdoc JSON backendC-bugCategory: This is a bug.T-rustdocRelevant to the rustdoc team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions