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

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

Open
LukeMathWalker opened this issue Aug 26, 2022 · 6 comments
Open

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

LukeMathWalker opened this issue Aug 26, 2022 · 6 comments
Labels
A-rustdoc-json Area: Rustdoc JSON backend C-bug Category: This is a bug. T-rustdoc Relevant to the rustdoc team, which will review and decide on the PR/issue.

Comments

@LukeMathWalker
Copy link
Contributor

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

@LukeMathWalker LukeMathWalker added the C-bug Category: This is a bug. label Aug 26, 2022
@GuillaumeGomez GuillaumeGomez added T-rustdoc Relevant to the rustdoc team, which will review and decide on the PR/issue. A-rustdoc-json Area: Rustdoc JSON backend labels Aug 26, 2022
@Enselic
Copy link
Member

Enselic commented Aug 27, 2022

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

@aDotInTheVoid
Copy link
Member

aDotInTheVoid commented Aug 27, 2022

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

@aDotInTheVoid
Copy link
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" } ```

@aDotInTheVoid
Copy link
Member

aDotInTheVoid commented Aug 27, 2022

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"
}

@LukeMathWalker
Copy link
Contributor Author

LukeMathWalker commented Aug 27, 2022

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.

@obi1kenobi
Copy link
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
Labels
A-rustdoc-json Area: Rustdoc JSON backend C-bug Category: This is a bug. T-rustdoc Relevant to the rustdoc team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

5 participants