fd2771d51dfa6526d51778a1a3e2e553aa75290c
braney
  Tue Sep 1 10:37:16 2026 -0700
trackDbConditions: follow a setting to where its value is used, refs #37908

The first pass only saw a condition when it enclosed the read.  That misses the
commonest shape in the drawing code: the setting is read plainly at the top of a
loader, carried into a struct, and used far below behind a test of a different
setting.  bamColorTag came back unconditional even though it does nothing unless
bamColorMode is tag.

So follow the value.  A name that holds exactly one setting across a file is
taken to carry it, including into a struct field of the same name, which is how
the value usually travels.  Then find where the value is used and intersect the
conditions guarding those uses.

Two distinctions do the work.  A mention at paren depth zero is one side of an
assignment or an element of an initializer list, which only moves the value
somewhere else, so it is not a use; inside a call it is an argument and it is.
Counting the struct initializer as a use put an unguarded site in the set and
emptied every intersection.  And matching drops the field prefix, so the test
written sameString(colorMode, ...) in the loader is the same condition as
sameString(btd->colorMode, ...) in the drawer.

These are reported as "when used" and kept apart from "always".  The every-path
conditions are necessary by construction; a use-site condition is only as good
as the set of uses found, so it is a strong hint rather than a claim, and it
keeps the strict key rather than the loose one for that reason.

43 settings in the render scope are read plainly and used only under a
condition, 21 of them documented.  Among them: bamColorTag needs
bamColorMode=tag, pairSearchRange needs pairEndsByName, speciesCodonDefault
needs mafChain and frames, and speciesOrder, speciesGroups and speciesDefaultOff
turn out to gate each other.

diff --git src/hg/utils/trackDbConditions/trackDbConditions.py src/hg/utils/trackDbConditions/trackDbConditions.py
index db778baecf7..d5691c18ab5 100755
--- src/hg/utils/trackDbConditions/trackDbConditions.py
+++ src/hg/utils/trackDbConditions/trackDbConditions.py
@@ -141,88 +141,104 @@
                 json.dump(reads, f)
 
     accessors = accessorMap(reads)
     settings = {}
     for read in reads:
         if not read["tdb"]:
             continue                       # a page cart variable, not a track setting
         key = (read["name"], read["scope"])
         entry = settings.setdefault(key, {"name": read["name"], "scope": read["scope"],
                                           "sites": [], "unknown": False})
         conds = []
         for cond in hc.realConds(read):
             kind, named = classify(cond, accessors)
             conds.append({"text": cond["text"], "kind": kind, "names": named,
                           "file": cond["file"], "line": cond["line"]})
+        used = []
+        for cond in read.get("useConds", []):
+            if hc.isNoise(cond):
+                continue
+            kind, named = classify(cond, accessors)
+            used.append({"text": cond["text"], "kind": kind, "names": named,
+                         "file": cond["file"], "line": cond["line"]})
         entry["sites"].append({"file": read["file"], "line": read["line"],
                                "func": read["func"], "reader": read["reader"],
-                               "conds": conds})
+                               "conds": conds, "used": used})
         if read["callerUnknown"]:
             entry["unknown"] = True
 
     for entry in settings.values():
         perSite = [{condId(c): c for c in s["conds"]} for s in entry["sites"]]
         always, sometimes = {}, {}
         if perSite:
             common = set(perSite[0])
             for one in perSite[1:]:
                 common &= set(one)
             for one in perSite:
                 for cid, cond in one.items():
                     (always if cid in common else sometimes)[cid] = cond
         entry["always"] = sorted(always.values(), key=lambda c: (KIND_ORDER.index(c["kind"]),
                                                                 c["text"]))
         entry["sometimes"] = sorted(sometimes.values(), key=lambda c: (KIND_ORDER.index(c["kind"]),
                                                                       c["text"]))
+        perUse = [{condId(c): c for c in s["used"]} for s in entry["sites"] if s["used"]]
+        whenUsed = {}
+        if perUse:
+            common = set(perUse[0])
+            for one in perUse[1:]:
+                common &= set(one)
+            whenUsed = {cid: c for one in perUse for cid, c in one.items() if cid in common}
+        entry["whenUsed"] = sorted(whenUsed.values(),
+                                   key=lambda c: (KIND_ORDER.index(c["kind"]), c["text"]))
     return list(settings.values())
 
 
 def documented(kentSrc=None):
     """The settings trackDbLibrary.shtml describes, so the report can say which."""
     kentSrc = kentSrc or os.environ.get("KENT_SRC") or "."
     path = os.path.join(kentSrc, SETTINGS_JSON)
     if not os.path.exists(path):
         return {}
     with open(path) as f:
         doc = json.load(f)
     return {s["key"]: s for s in doc["settings"]}
 
 
 def baselineNames():
     if not os.path.exists(BASELINE):
         return set()
     with open(BASELINE) as f:
         return {ln.strip() for ln in f if ln.strip() and not ln.startswith("#")}
 
 
 def conditionedNames(entries, docs, scope="render"):
     """Documented settings that carry an always-condition, as scope:name."""
     return {"%s:%s" % (e["scope"], e["name"]) for e in entries
-            if e["scope"] == scope and e["always"] and e["name"] in docs}
+            if e["scope"] == scope and (e["always"] or e["whenUsed"]) and e["name"] in docs}
 
 
 def showSetting(entry, docs, verbose=False):
     doc = docs.get(entry["name"])
     types = doc["types"] if doc else None
     if isinstance(types, list):
         types = ", ".join(types)
     print("%s  [%s]" % (entry["name"], entry["scope"]))
     if doc:
         print("    documented for: %s" % (types or "?"))
     else:
         print("    not in trackDbLibrary.shtml")
-    groups = [("always", entry["always"])]
+    groups = [("always", entry["always"]), ("when used", entry["whenUsed"])]
     if verbose:
         groups.append(("sometimes", entry["sometimes"]))
     elif entry["sometimes"]:
         print("    (%d more conditions hold at some of its %d read sites; --verbose to see)"
               % (len(entry["sometimes"]), len(entry["sites"])))
     for label, conds in groups:
         for cond in conds:
             names = (" -> " + ", ".join(cond["names"])) if cond["names"] else ""
             print("    %-9s %-12s %s%s" % (label, cond["kind"], cond["text"][:88], names))
     if entry["unknown"]:
         print("    (some call paths could not be followed, so this is a floor)")
     if verbose:
         for site in entry["sites"]:
             print("      %s:%d  %s()" % (site["file"], site["line"], site["func"]))
 
@@ -281,48 +297,51 @@
             problems += 1
         sys.exit(1 if problems else 0)
 
     scopes = list(hc.SCOPES) if args.scope == "both" else [args.scope]
     picked = [e for e in entries if e["scope"] in scopes]
     if args.documented:
         picked = [e for e in picked if e["name"] in docs]
     if args.setting:
         picked = [e for e in picked if e["name"] == args.setting]
         for entry in picked:
             showSetting(entry, docs, verbose=True)
         if not picked:
             print("no read of %s found in %s" % (args.setting, ", ".join(scopes)))
         return
 
-    conditioned = [e for e in picked if e["always"] or e["sometimes"]]
+    conditioned = [e for e in picked if e["always"] or e["sometimes"] or e["whenUsed"]]
     if args.kind:
         conditioned = [e for e in conditioned
                        if any(c["kind"] == args.kind for c in e["always"] + e["sometimes"])]
     if args.surprising:
         conditioned = [e for e in conditioned
-                       if any(c["kind"] in SURPRISING for c in e["always"])]
+                       if any(c["kind"] in SURPRISING for c in e["always"] + e["whenUsed"])]
 
     if args.list or args.kind or args.surprising:
         for entry in sorted(conditioned, key=lambda e: (e["scope"], e["name"].lower())):
             showSetting(entry, docs, args.verbose)
             print()
 
     inDocs = [e for e in conditioned if e["name"] in docs]
     always = [e for e in conditioned if e["always"]]
+    used = [e for e in conditioned if e["whenUsed"]]
     print("settings read           %d  (%s)" % (len(picked), ", ".join(scopes)))
     print("with any condition      %d" % len(conditioned))
     print("with an always-condition %d" % len(always))
     print("of those, documented    %d" % len([e for e in always if e["name"] in docs]))
+    print("read plainly, used only under a condition  %d  (documented %d)"
+          % (len(used), len([e for e in used if e["name"] in docs])))
     print("documented and conditional at all: %d" % len(inDocs))
     print()
     tally = collections.Counter()
     for entry in conditioned:
-        for cond in entry["always"]:
+        for cond in entry["always"] + entry["whenUsed"]:
             tally[cond["kind"]] += 1
     print("always-conditions by kind:")
     for kind in KIND_ORDER:
         if tally[kind]:
             print("  %-14s %3d   %s" % (kind, tally[kind], KIND_TEXT[kind]))
 
 
 if __name__ == "__main__":
     main()