about summary refs log tree commit diff
path: root/src/etc/check_missing_items.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/etc/check_missing_items.py')
-rw-r--r--src/etc/check_missing_items.py189
1 files changed, 189 insertions, 0 deletions
diff --git a/src/etc/check_missing_items.py b/src/etc/check_missing_items.py
new file mode 100644
index 00000000000..c7ca0134f9c
--- /dev/null
+++ b/src/etc/check_missing_items.py
@@ -0,0 +1,189 @@
+#!/usr/bin/env python
+
+# This test ensures that every ID in the produced json actually resolves to an item either in
+# `index` or `paths`. It DOES NOT check that the structure of the produced json is actually in
+# any way correct, for example an empty map would pass.
+
+# FIXME: Better error output
+
+import sys
+import json
+
+crate = json.load(open(sys.argv[1]))
+
+
+def get_local_item(item_id):
+    if item_id in crate["index"]:
+        return crate["index"][item_id]
+    print("Missing local ID:", item_id)
+    sys.exit(1)
+
+
+# local IDs have to be in `index`, external ones can sometimes be in `index` but otherwise have
+# to be in `paths`
+def valid_id(item_id):
+    return item_id in crate["index"] or item_id[0] != "0" and item_id in crate["paths"]
+
+
+def check_generics(generics):
+    for param in generics["params"]:
+        check_generic_param(param)
+    for where_predicate in generics["where_predicates"]:
+        if "bound_predicate" in where_predicate:
+            pred = where_predicate["bound_predicate"]
+            check_type(pred["ty"])
+            for bound in pred["bounds"]:
+                check_generic_bound(bound)
+        elif "region_predicate" in where_predicate:
+            pred = where_predicate["region_predicate"]
+            for bound in pred["bounds"]:
+                check_generic_bound(bound)
+        elif "eq_predicate" in where_predicate:
+            pred = where_predicate["eq_predicate"]
+            check_type(pred["rhs"])
+            check_type(pred["lhs"])
+
+
+def check_generic_param(param):
+    if "type" in param["kind"]:
+        ty = param["kind"]["type"]
+        if ty["default"]:
+            check_type(ty["default"])
+        for bound in ty["bounds"]:
+            check_generic_bound(bound)
+    elif "const" in param["kind"]:
+        check_type(param["kind"]["const"])
+
+
+def check_generic_bound(bound):
+    if "trait_bound" in bound:
+        for param in bound["trait_bound"]["generic_params"]:
+            check_generic_param(param)
+        check_type(bound["trait_bound"]["trait"])
+
+
+def check_decl(decl):
+    for (_name, ty) in decl["inputs"]:
+        check_type(ty)
+    if decl["output"]:
+        check_type(decl["output"])
+
+
+def check_type(ty):
+    if ty["kind"] == "resolved_path":
+        for bound in ty["inner"]["param_names"]:
+            check_generic_bound(bound)
+        args = ty["inner"]["args"]
+        if args:
+            if "angle_bracketed" in args:
+                for arg in args["angle_bracketed"]["args"]:
+                    if "type" in arg:
+                        check_type(arg["type"])
+                    elif "const" in arg:
+                        check_type(arg["const"]["type"])
+                for binding in args["angle_bracketed"]["bindings"]:
+                    if "equality" in binding["binding"]:
+                        check_type(binding["binding"]["equality"])
+                    elif "constraint" in binding["binding"]:
+                        for bound in binding["binding"]["constraint"]:
+                            check_generic_bound(bound)
+            elif "parenthesized" in args:
+                for ty in args["parenthesized"]["inputs"]:
+                    check_type(ty)
+                if args["parenthesized"]["output"]:
+                    check_type(args["parenthesized"]["output"])
+        if not valid_id(ty["inner"]["id"]):
+            print("Type contained an invalid ID:", ty["inner"]["id"])
+            sys.exit(1)
+    elif ty["kind"] == "tuple":
+        for ty in ty["inner"]:
+            check_type(ty)
+    elif ty["kind"] == "slice":
+        check_type(ty["inner"])
+    elif ty["kind"] == "impl_trait":
+        for bound in ty["inner"]:
+            check_generic_bound(bound)
+    elif ty["kind"] in ("raw_pointer", "borrowed_ref", "array"):
+        check_type(ty["inner"]["type"])
+    elif ty["kind"] == "function_pointer":
+        for param in ty["inner"]["generic_params"]:
+            check_generic_param(param)
+        check_decl(ty["inner"]["inner"])
+    elif ty["kind"] == "qualified_path":
+        check_type(ty["inner"]["self_type"])
+        check_type(ty["inner"]["trait"])
+
+
+work_list = set([crate["root"]])
+visited = work_list.copy()
+
+while work_list:
+    current = work_list.pop()
+    visited.add(current)
+    item = get_local_item(current)
+    # check intradoc links
+    for (_name, link) in item["links"].items():
+        if not valid_id(link):
+            print("Intra-doc link contains invalid ID:", link)
+
+    # check all fields that reference types such as generics as well as nested items
+    # (modules, structs, traits, and enums)
+    if item["kind"] == "module":
+        work_list |= set(item["inner"]["items"]) - visited
+    elif item["kind"] == "struct":
+        check_generics(item["inner"]["generics"])
+        work_list |= (
+            set(item["inner"]["fields"]) | set(item["inner"]["impls"])
+        ) - visited
+    elif item["kind"] == "struct_field":
+        check_type(item["inner"])
+    elif item["kind"] == "enum":
+        check_generics(item["inner"]["generics"])
+        work_list |= (
+            set(item["inner"]["variants"]) | set(item["inner"]["impls"])
+        ) - visited
+    elif item["kind"] == "variant":
+        if item["inner"]["variant_kind"] == "tuple":
+            for ty in item["inner"]["variant_inner"]:
+                check_type(ty)
+        elif item["inner"]["variant_kind"] == "struct":
+            work_list |= set(item["inner"]["variant_inner"]) - visited
+    elif item["kind"] in ("function", "method"):
+        check_generics(item["inner"]["generics"])
+        check_decl(item["inner"]["decl"])
+    elif item["kind"] in ("static", "constant", "assoc_const"):
+        check_type(item["inner"]["type"])
+    elif item["kind"] == "typedef":
+        check_type(item["inner"]["type"])
+        check_generics(item["inner"]["generics"])
+    elif item["kind"] == "opaque_ty":
+        check_generics(item["inner"]["generics"])
+        for bound in item["inner"]["bounds"]:
+            check_generic_bound(bound)
+    elif item["kind"] == "trait_alias":
+        check_generics(item["inner"]["params"])
+        for bound in item["inner"]["bounds"]:
+            check_generic_bound(bound)
+    elif item["kind"] == "trait":
+        check_generics(item["inner"]["generics"])
+        for bound in item["inner"]["bounds"]:
+            check_generic_bound(bound)
+        work_list |= (
+            set(item["inner"]["items"]) | set(item["inner"]["implementors"])
+        ) - visited
+    elif item["kind"] == "impl":
+        check_generics(item["inner"]["generics"])
+        if item["inner"]["trait"]:
+            check_type(item["inner"]["trait"])
+        if item["inner"]["blanket_impl"]:
+            check_type(item["inner"]["blanket_impl"])
+        check_type(item["inner"]["for"])
+        for assoc_item in item["inner"]["items"]:
+            if not valid_id(assoc_item):
+                print("Impl block referenced a missing ID:", assoc_item)
+                sys.exit(1)
+    elif item["kind"] == "assoc_type":
+        for bound in item["inner"]["bounds"]:
+            check_generic_bound(bound)
+        if item["inner"]["default"]:
+            check_type(item["inner"]["default"])