import collections
import json
import sys
doc = json.load(open(sys.argv[1]))
index = {int(k): v for k, v in doc["index"].items()}
paths = {int(k): v for k, v in doc["paths"].items()}
nameable = set()
def walk(item_id):
item = index.get(item_id)
if item is None or item_id in nameable:
return
nameable.add(item_id)
use = item["inner"].get("use")
if use is not None:
if use["id"] is not None:
walk(use["id"])
return
module = item["inner"].get("module")
if module is not None:
for child_id in module["items"]:
walk(child_id)
walk(doc["root"])
def referenced(node, out):
if isinstance(node, dict):
for key, value in node.items():
if key in ("resolved_path", "trait") and isinstance(value, dict):
if "id" in value:
out.add(value["id"])
referenced(value, out)
elif isinstance(node, list):
for element in node:
referenced(element, out)
owner = {}
for item in index.values():
inner = item["inner"]
members = []
name = item.get("name")
if "struct" in inner:
kind = inner["struct"]["kind"]
if isinstance(kind, dict):
members = kind.get("tuple") or kind.get("plain", {}).get("fields", [])
elif "variant" in inner:
kind = inner["variant"]["kind"]
if isinstance(kind, dict):
members = kind.get("tuple") or kind.get("struct", {}).get("fields", [])
elif "impl" in inner:
members = inner["impl"]["items"]
for_type = inner["impl"]["for"].get("resolved_path", {})
name = for_type.get("path", "").rsplit("::", 1)[-1]
for member_id in members or []:
if member_id is not None and name:
owner[member_id] = name
leaks = collections.defaultdict(set)
for item_id, item in index.items():
inner = item["inner"]
if "trait" in inner:
continue
ids = set()
referenced(inner, ids)
for type_id in ids:
if type_id in nameable or type_id not in paths:
continue
if paths[type_id].get("crate_id", 0) != 0:
continue
name = item.get("name") or "?"
if item_id in owner:
name = f"{owner[item_id]}::{name}"
leaks["::".join(paths[type_id]["path"])].add(name)
print(f"reached {len(nameable)} public items from the crate root")
if not leaks:
print("no unnameable types reachable through it")
sys.exit(0)
print("\nreachable by value, impossible to name:\n")
for type_path, users in sorted(leaks.items()):
print(f" {type_path}")
print(f" through: {', '.join(sorted(users))}")
print(
"\nRe-export each from lib.rs (sealing it #[non_exhaustive] where the format\n"
"can grow), or take it out of the public signature that reaches it."
)
sys.exit(1)