#!/usr/bin/env bash # prohibitions.sh — enforce boundary prohibitions as a QUERY over relations the # compiler emitted, rather than as a rule the compiler contains. # # A prohibition is a containment relation over the call graph: "these calls may # appear only inside a fn carrying construct C". The compiler's job is to say # what it saw — who calls what, who carries what, who prohibits what. Deciding # whether that is legal is a query, and a query does not belong in an emitter. # # Detection still happens at BUILD time. What moved is where the rule and the # checker live, which is what "a #error has no runtime" was hiding. # # usage: prohibitions.sh # exit 0 = clean; exit N = N violations set -uo pipefail REL="${1:?usage: prohibitions.sh }" [ -f "$REL" ] || { echo "no relations file: $REL" >&2; exit 0; } V=0 # construct -> prohibited names while read -r construct _ rest; do [ "${rest#prohibits:}" = "$rest" ] && continue names="${rest#prohibits:}" IFS=',' read -ra NAMES <<< "$names" for n in "${NAMES[@]}"; do # every fn that calls a prohibited name while read -r caller _ callee; do [ "$callee" = "$n" ] || continue # ...must carry the owning construct if ! grep -qx "$caller calls @$construct" "$REL"; then printf 'boundary violation: %s may only be called from an @%s fn, but %s is not one\n' \ "$n" "$construct" "$caller" V=$((V+1)) fi done < <(sort -u "$REL") done done < <(sort -u "$REL") [ "$V" -eq 0 ] && echo "prohibitions: clean" exit "$V"