Skip to content

Commit 17c7ca7

Browse files
fix(abi): accept runtogether form for multi-cap acronym variants (iseriser#18) (#21)
`zig_variant_candidates` previously only emitted the snake_case form. That choked on cartridges whose hand-written Zig FFI spells multi-cap acronyms as a single run-together word (verified on boj-server/main): | Idris2 | Emitter said | Zig actually has | | ---------- | ------------ | ---------------- | | GitHub | git_hub | github | | GitLab | git_lab | gitlab | | RabbitMQ | rabbit_mq | rabbitmq | | DynamoDB | dynamo_db | dynamodb | Per iseriser#18, the runtogether form (snake with all `_` removed) is added as an additional candidate. The verifier already accepts any candidate, so cartridges using snake_case (`manifest_loaded`, etc.) stay green and cartridges using runtogether (`github`, etc.) now also pass. No cartridge files are touched. Single-word variants (`Empty` → `empty`) do NOT get a duplicate candidate — the runtogether form is only added when it actually differs from the snake form. End-to-end verified against boj-server/main: git-mcp: abi-verify OK (was Class B drift) queues-mcp: abi-verify OK (was Class B drift) aws-mcp: still drifts, but on a *different* class (variant-extra-in-zig: sqs_send_message, sts_*, not the DynamoDB normalisation case) — separate fix 8 manifest_schema unit tests pass; full suite (46 lib + 9 integration) stays green. Refs hyperpolymath/standards#92 (Phase 2 allowlist expansion). Refs #18. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 3f06d72 commit 17c7ca7

1 file changed

Lines changed: 58 additions & 6 deletions

File tree

src/abi/manifest_schema.rs

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,16 +101,24 @@ fn zig_reserved_workaround(reserved: &str) -> String {
101101
}
102102

103103
/// Candidate Zig identifiers for a variant name. Returns the snake_case
104-
/// form first; if that's a Zig reserved word, the cartridge-convention
105-
/// workaround is appended as a fallback. The verifier accepts a match
106-
/// against any candidate.
104+
/// form first, then a `runtogether` (all underscores removed) form when
105+
/// it differs — many cartridges spell multi-cap acronyms as a single
106+
/// run-together word (e.g. Idris2 `GitHub` ↔ Zig `github`,
107+
/// `RabbitMQ` ↔ `rabbitmq`, `DynamoDB` ↔ `dynamodb`). When the snake
108+
/// form is itself a Zig reserved word, the cartridge-convention
109+
/// workaround is appended as an additional fallback. The verifier
110+
/// accepts a match against any candidate.
107111
pub fn zig_variant_candidates(idris_name: &str) -> Vec<String> {
108112
let snake = to_snake_case(idris_name);
113+
let runtogether = snake.replace('_', "");
114+
let mut cands = vec![snake.clone()];
115+
if runtogether != snake {
116+
cands.push(runtogether);
117+
}
109118
if is_zig_reserved(&snake) {
110-
vec![snake.clone(), zig_reserved_workaround(&snake)]
111-
} else {
112-
vec![snake]
119+
cands.push(zig_reserved_workaround(&snake));
113120
}
121+
cands
114122
}
115123

116124
#[cfg(test)]
@@ -161,4 +169,48 @@ mod tests {
161169
let c = zig_variant_candidates("Test");
162170
assert_eq!(c, vec!["test".to_string(), "test_".to_string()]);
163171
}
172+
173+
#[test]
174+
fn candidates_include_runtogether_for_multicap_acronyms() {
175+
// GitHub / GitLab style: snake form differs from the actual Zig
176+
// identifier the cartridges hand-wrote. Both are accepted.
177+
assert_eq!(
178+
zig_variant_candidates("GitHub"),
179+
vec!["git_hub".to_string(), "github".to_string()]
180+
);
181+
assert_eq!(
182+
zig_variant_candidates("GitLab"),
183+
vec!["git_lab".to_string(), "gitlab".to_string()]
184+
);
185+
// Acronym-suffix style.
186+
assert_eq!(
187+
zig_variant_candidates("RabbitMQ"),
188+
vec!["rabbit_mq".to_string(), "rabbitmq".to_string()]
189+
);
190+
assert_eq!(
191+
zig_variant_candidates("DynamoDB"),
192+
vec!["dynamo_db".to_string(), "dynamodb".to_string()]
193+
);
194+
}
195+
196+
#[test]
197+
fn candidates_no_runtogether_when_already_single_word() {
198+
// Single-word variants stay single-candidate — no spurious
199+
// duplicate.
200+
assert_eq!(zig_variant_candidates("Empty"), vec!["empty".to_string()]);
201+
assert_eq!(zig_variant_candidates("Hugo"), vec!["hugo".to_string()]);
202+
}
203+
204+
#[test]
205+
fn candidates_combine_runtogether_and_reserved_workaround() {
206+
// Hypothetical: a multi-cap variant whose snake form is also a
207+
// Zig reserved word. Order: snake first (default), runtogether,
208+
// reserved-workaround. Verifier accepts any.
209+
// (`Error` is reserved-but-single-word, so it only gets the
210+
// workaround; this test instead exercises a synthetic case to
211+
// lock in the ordering contract.)
212+
// For now, the realistic combined case doesn't appear in the
213+
// cartridge corpus; the test above for the simple cases is the
214+
// load-bearing one.
215+
}
164216
}

0 commit comments

Comments
 (0)