Per ruling mind-launch-forge-scanner-bound: compile_fail doctests for the struct literal and each derive-provided path on Grant; forbid(unsafe_code) in the library; the gate property draws the mind's own instance from the same slug generator as the Grant's and tests pairs; Persona claim shapes are drawn and rotated by seed; the path-literal and static-keyword scanners are deleted from the public-surface test, whose limits are written at its top. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
256 lines
12 KiB
Rust
256 lines
12 KiB
Rust
//! The library's public surface, pinned as a tripwire.
|
|
//!
|
|
//! This reads the library's source the way a consumer sees it and compares
|
|
//! every `pub` item with its signature, every `pub` field, the derives and the
|
|
//! header of every impl, to `public_surface.txt`. A new `pub fn` under any name,
|
|
//! a `pub static`, a `Clone` or `Default` derived onto `Grant`, a new way to
|
|
//! reach the brake from outside: each is a line the snapshot does not have, and
|
|
//! the test fails. A deliberate change is a change to `public_surface.txt` in the
|
|
//! same commit, where a reviewer sees it; the failure prints the full current
|
|
//! surface to copy from.
|
|
//!
|
|
//! What it sees: items reached through `mod` declarations and module files,
|
|
//! `cfg(test)` skipped (a test-only item is not the library), the `unix` and
|
|
//! `windows` splits read on both sides. An item macro, `macro_rules!`, `extern`
|
|
//! block, `#[path]`, an exported-symbol attribute or any other `cfg` fails the
|
|
//! test outright, because each would hide an item from this reader.
|
|
//!
|
|
//! What it cannot see, by design: items nested in function or `const _` bodies
|
|
//! (an `impl Grant` inside a const block), and what a function body does. It
|
|
//! proves nothing about how the control root is derived, and it does not defend
|
|
//! the crate against edits to its own source. That bound is ruling
|
|
//! `eureka-body:ruling:mind-launch-forge-scanner-bound`: the Grant is held by the
|
|
//! type system (private fields, no constructor, the `compile_fail` examples on
|
|
//! `Grant`), unsafe code is forbidden in the library, and changes to
|
|
//! `control.rs` and `launch.rs` are defended by review and by the root-owned
|
|
//! brake check at the unit. Do not extend this file into a walker of bodies.
|
|
|
|
use std::collections::BTreeSet;
|
|
use std::fs;
|
|
use std::path::Path;
|
|
|
|
use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
|
|
use quote::ToTokens;
|
|
use syn::{Attribute, Fields, ImplItem, Item, Visibility};
|
|
|
|
const SNAPSHOT: &str = include_str!("public_surface.txt");
|
|
|
|
/// Attributes that would put an item outside this reader's sight or export it by name.
|
|
const HIDING: [&str; 7] = ["path", "no_mangle", "export_name", "link_section", "macro_export", "macro_use", "cfg_attr"];
|
|
|
|
fn is_pub(visibility: &Visibility) -> bool {
|
|
matches!(visibility, Visibility::Public(_))
|
|
}
|
|
|
|
fn without_docs(stream: TokenStream) -> TokenStream {
|
|
let tokens: Vec<TokenTree> = stream.into_iter().collect();
|
|
let mut out = Vec::new();
|
|
let mut at = 0;
|
|
while at < tokens.len() {
|
|
if matches!(&tokens[at], TokenTree::Punct(p) if p.as_char() == '#') {
|
|
let mut group_at = at + 1;
|
|
if matches!(tokens.get(group_at), Some(TokenTree::Punct(p)) if p.as_char() == '!') {
|
|
group_at += 1;
|
|
}
|
|
if let Some(TokenTree::Group(group)) = tokens.get(group_at) {
|
|
let is_doc = group.delimiter() == Delimiter::Bracket
|
|
&& matches!(group.stream().into_iter().next(), Some(TokenTree::Ident(name)) if name == "doc");
|
|
if is_doc {
|
|
at = group_at + 1;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
match &tokens[at] {
|
|
TokenTree::Group(group) => out.push(TokenTree::Group(Group::new(group.delimiter(), without_docs(group.stream())))),
|
|
other => out.push(other.clone()),
|
|
}
|
|
at += 1;
|
|
}
|
|
out.into_iter().collect()
|
|
}
|
|
|
|
fn text(node: impl ToTokens) -> String {
|
|
without_docs(node.to_token_stream()).to_string()
|
|
}
|
|
|
|
fn attributes(item: &Item) -> &[Attribute] {
|
|
match item {
|
|
Item::Const(i) => &i.attrs,
|
|
Item::Enum(i) => &i.attrs,
|
|
Item::ExternCrate(i) => &i.attrs,
|
|
Item::Fn(i) => &i.attrs,
|
|
Item::ForeignMod(i) => &i.attrs,
|
|
Item::Impl(i) => &i.attrs,
|
|
Item::Macro(i) => &i.attrs,
|
|
Item::Mod(i) => &i.attrs,
|
|
Item::Static(i) => &i.attrs,
|
|
Item::Struct(i) => &i.attrs,
|
|
Item::Trait(i) => &i.attrs,
|
|
Item::TraitAlias(i) => &i.attrs,
|
|
Item::Type(i) => &i.attrs,
|
|
Item::Union(i) => &i.attrs,
|
|
Item::Use(i) => &i.attrs,
|
|
_ => &[],
|
|
}
|
|
}
|
|
|
|
/// Whether the library build contains what these attributes sit on.
|
|
fn in_library(attrs: &[Attribute], at: &str) -> bool {
|
|
let mut keep = true;
|
|
for attr in attrs {
|
|
let rendered = attr.to_token_stream().to_string().replace(' ', "");
|
|
assert!(!HIDING.iter().any(|word| attr.path().is_ident(*word)) && !rendered.contains("no_mangle") && !rendered.contains("export_name"), "{at}: `{rendered}` hides an item from the surface test");
|
|
if attr.path().is_ident("cfg") {
|
|
match rendered.as_str() {
|
|
"#[cfg(test)]" => keep = false,
|
|
// Both sides of a platform split are the library, and both are read.
|
|
"#[cfg(not(test))]" | "#[cfg(unix)]" | "#[cfg(not(unix))]" | "#[cfg(windows)]" | "#[cfg(not(windows))]" => {}
|
|
other => panic!("{at}: `{other}`: the surface test reads only cfg(test), cfg(not(test)) and the unix/windows splits"),
|
|
}
|
|
}
|
|
}
|
|
keep
|
|
}
|
|
|
|
fn kept_fields(fields: &Fields) -> Vec<String> {
|
|
match fields {
|
|
Fields::Named(named) => named.named.iter().filter(|f| is_pub(&f.vis)).map(|f| format!("{}: {}", text(&f.ident), text(&f.ty))).collect(),
|
|
Fields::Unnamed(unnamed) => unnamed.unnamed.iter().enumerate().filter(|(_, f)| is_pub(&f.vis)).map(|(i, f)| format!("{i}: {}", text(&f.ty))).collect(),
|
|
Fields::Unit => Vec::new(),
|
|
}
|
|
}
|
|
|
|
fn derives(attrs: &[Attribute]) -> String {
|
|
attrs.iter().filter(|a| a.path().is_ident("derive") || a.path().is_ident("repr")).map(text).collect::<Vec<_>>().join(" ")
|
|
}
|
|
|
|
fn walk(items: &[Item], path: &str, dir: &Path, out: &mut BTreeSet<String>) {
|
|
for item in items {
|
|
if !in_library(attributes(item), path) {
|
|
continue;
|
|
}
|
|
match item {
|
|
Item::Fn(f) if is_pub(&f.vis) => {
|
|
out.insert(format!("{path}: {}", text(&f.sig)));
|
|
}
|
|
Item::Const(c) if is_pub(&c.vis) => {
|
|
out.insert(format!("{path}: const {} : {}", c.ident, text(&c.ty)));
|
|
}
|
|
Item::Static(s) if is_pub(&s.vis) => {
|
|
out.insert(format!("{path}: static {} : {} mutable={}", s.ident, text(&s.ty), matches!(s.mutability, syn::StaticMutability::Mut(_))));
|
|
}
|
|
Item::Struct(s) if is_pub(&s.vis) => {
|
|
out.insert(format!("{path}: struct {}{} {} pub-fields[{}]", s.ident, text(&s.generics), derives(&s.attrs), kept_fields(&s.fields).join(", ")));
|
|
}
|
|
Item::Enum(e) if is_pub(&e.vis) => {
|
|
out.insert(format!("{path}: {}", text(Item::Enum(e.clone()))));
|
|
}
|
|
Item::Union(u) if is_pub(&u.vis) => {
|
|
out.insert(format!("{path}: {}", text(Item::Union(u.clone()))));
|
|
}
|
|
Item::Trait(t) if is_pub(&t.vis) => {
|
|
out.insert(format!("{path}: {}", text(Item::Trait(t.clone()))));
|
|
}
|
|
Item::TraitAlias(t) if is_pub(&t.vis) => {
|
|
out.insert(format!("{path}: {}", text(Item::TraitAlias(t.clone()))));
|
|
}
|
|
Item::Type(t) if is_pub(&t.vis) => {
|
|
out.insert(format!("{path}: {}", text(Item::Type(t.clone()))));
|
|
}
|
|
Item::Use(u) if is_pub(&u.vis) => {
|
|
out.insert(format!("{path}: {}", text(u)));
|
|
}
|
|
Item::Impl(i) => {
|
|
let header = match &i.trait_ {
|
|
Some((bang, tr, _)) => format!("impl{} {}{} for {}", text(&i.generics), text(bang), text(tr), text(&i.self_ty)),
|
|
None => format!("impl{} {}", text(&i.generics), text(&i.self_ty)),
|
|
};
|
|
out.insert(format!("{path}: {header}"));
|
|
for member in &i.items {
|
|
let attrs = match member {
|
|
ImplItem::Const(m) => &m.attrs,
|
|
ImplItem::Fn(m) => &m.attrs,
|
|
ImplItem::Type(m) => &m.attrs,
|
|
ImplItem::Macro(m) => &m.attrs,
|
|
_ => &[][..],
|
|
};
|
|
if !in_library(attrs, path) {
|
|
continue;
|
|
}
|
|
if i.trait_.is_none() {
|
|
match member {
|
|
ImplItem::Fn(f) if is_pub(&f.vis) => {
|
|
out.insert(format!("{path}: impl {}: {}", text(&i.self_ty), text(&f.sig)));
|
|
}
|
|
ImplItem::Const(c) if is_pub(&c.vis) => {
|
|
out.insert(format!("{path}: impl {}: const {} : {}", text(&i.self_ty), c.ident, text(&c.ty)));
|
|
}
|
|
ImplItem::Type(t) if is_pub(&t.vis) => {
|
|
out.insert(format!("{path}: impl {}: {}", text(&i.self_ty), text(t)));
|
|
}
|
|
ImplItem::Macro(_) | ImplItem::Verbatim(_) => panic!("{path}: an impl member the surface test cannot read"),
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Item::Mod(m) => {
|
|
let name = m.ident.to_string();
|
|
if is_pub(&m.vis) {
|
|
out.insert(format!("{path}: mod {name}"));
|
|
}
|
|
let inner = format!("{path}::{name}");
|
|
let below = dir.join(&name);
|
|
match &m.content {
|
|
Some((_, content)) => walk(content, &inner, &below, out),
|
|
None => {
|
|
let file = [dir.join(format!("{name}.rs")), below.join("mod.rs")].into_iter().find(|candidate| candidate.exists());
|
|
let file = file.unwrap_or_else(|| panic!("{inner}: no source file"));
|
|
let parsed = syn::parse_file(&fs::read_to_string(&file).unwrap()).unwrap_or_else(|e| panic!("{}: {e}", file.display()));
|
|
if in_library(&parsed.attrs, &inner) {
|
|
walk(&parsed.items, &inner, &below, out);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Item::Macro(_) => panic!("{path}: an item macro in library source: the surface test cannot see what it makes"),
|
|
Item::ForeignMod(_) | Item::ExternCrate(_) | Item::Verbatim(_) => panic!("{path}: an item the surface test cannot read"),
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn surface() -> BTreeSet<String> {
|
|
let src = Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
|
|
let parsed = syn::parse_file(&fs::read_to_string(src.join("lib.rs")).unwrap()).unwrap();
|
|
let mut out = BTreeSet::new();
|
|
walk(&parsed.items, "mind_body", &src, &mut out);
|
|
out
|
|
}
|
|
|
|
#[test]
|
|
fn the_librarys_public_surface_is_the_committed_one() {
|
|
let actual = surface();
|
|
let committed: BTreeSet<String> = SNAPSHOT.lines().filter(|line| !line.is_empty()).map(str::to_string).collect();
|
|
let added: Vec<_> = actual.difference(&committed).collect();
|
|
let removed: Vec<_> = committed.difference(&actual).collect();
|
|
assert!(
|
|
added.is_empty() && removed.is_empty(),
|
|
"the public surface changed.\nadded:\n{}\nremoved:\n{}\nthe full current surface, for tests/public_surface.txt:\n{}",
|
|
added.iter().map(|l| format!(" {l}")).collect::<Vec<_>>().join("\n"),
|
|
removed.iter().map(|l| format!(" {l}")).collect::<Vec<_>>().join("\n"),
|
|
actual.iter().cloned().collect::<Vec<_>>().join("\n"),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn the_reader_sees_what_it_claims_to() {
|
|
// The snapshot must hold the brake's own names, or the reader is blind to them.
|
|
let actual = surface();
|
|
for needle in ["mind_body::control: fn read_effective", "mind_body::control: struct Grant"] {
|
|
assert!(actual.iter().any(|line| line.contains(needle)), "{needle} is not in the surface");
|
|
}
|
|
// And nothing under cfg(test) leaked in.
|
|
assert!(!actual.iter().any(|line| line.contains("for_test") || line.contains("with_root") || line.contains("TEST_ROOT")), "a cfg(test) item is in the surface");
|
|
}
|