refactor(printf): dispatch -printf directives and escapes via constexpr LimitedMaps - #85
Merged
Conversation
…pr LimitedMaps Replaces FormatPrintf's two switch statements with constexpr mbo::container::MakeLimitedMap tables: kPrintfEscapes (char -> char) for the backslash escapes and kPrintfDirectives (char -> handler) for the % directives, keyed alphabetically and looked up with .find() - the same pattern engine/evaluate.cc's kDispatch uses for the primaries. The % handlers are captureless lambdas (void(std::string&, const Visit&)) so the table is constexpr-constructible. Behavior is unchanged: the existing -printf tests pass, and unknown directives/escapes still emit literally. Addresses the TODO that switch/case lists be alphabetical and use constexpr dispatch where possible. MatchesType/MatchesSize/TypeLetter are candidates for a follow-up sweep.
helly25
enabled auto-merge (squash)
June 22, 2026 21:58
helly25
added a commit
that referenced
this pull request
Jul 2, 2026
-grep PATTERN is the line-output companion of the -rxc content predicate: for each file it prints every content line matching an RE2 regex as path:line:text (grep's piped form), and returns true iff it printed a line (grep-like exit, so it composes with -o / -q). Non-regular, unreadable, and binary files yield nothing, reusing the content predicates' ContentToSearch (8 KiB NUL sniff). It is self-contained (its own pattern, pre-compiled like -rxc) and composes with the full find predicate set -- xff -mtime -7 -size +1M -grep TODO -- which is the value over piping to grep/rg: find-grade selection fused with the match output in one walk. -content / -rxc stay untouched as file filters. The -grep name, rejected earlier for a *matcher* predicate (grep's regex-flavor baggage), is unambiguous for a pattern-less-flavor output action; the flavor will live in --regextype (#85), not the name. xff-style extension (--config=find rejects it). Deferred to follow-ups: the -grep=FORMAT custom template with {line}/{text} fields, --regextype=EXACT|RE2 mode selection, {match}/{column} (-o), context (-A/-B/-C), and count (-c, #92). Tests: evaluate_test (emit shape + line numbers, regex not literal, no-match is false+silent, binary skip); run_test (path:line:text across the walk); grep_test.sh end-to-end (regex, binary skip, composition, find-style rejection). Self-doc: registry descriptor + kHelpText + design.md. bazel test //... green (39).
helly25
added a commit
that referenced
this pull request
Jul 2, 2026
The first -grep enhancement and the answer to "exact vs regex": a --regextype global selects how -grep reads its pattern. RE2 (the default) is the regex engine; EXACT matches the pattern as a literal substring per line (so '.' is a literal dot and an unparseable-as-regex pattern like 'foo(bar' is a fine literal). The grep-flavor MATCH and the PCRE engine are reserved for #85 and refused now with a clear usage error (exit 2), as is any unknown value. Wiring: run.cc ResolveGrepLiteral (RE2->false, EXACT->true, MATCH/PCRE/unknown-> InvalidArgument, refused before the walk) threads EvalContext::grep_literal; EvalGrep branches to a literal StrContains matcher instead of the pre-compiled RE2 one, so EXACT never depends on the pattern being a valid regex. -content/-rxc and find's own -regextype primary are untouched (dash-count convention: --regextype is the xff global superset; #85 unifies them). Self-doc: --regextype in globals.cc + kHelpText; design.md -grep note updated to "shipped RE2|EXACT". Tests: evaluate_test (EXACT literal dot + regex-metachar-as- literal), run_test (EXACT vs RE2 default, reserved value is a usage error), grep_test.sh e2e. bazel test //... green (39).
helly25
added a commit
that referenced
this pull request
Jul 6, 2026
Record the architecture decision that heavy/special libs (libarchive #83, pcre2 #85, future ones) are build-time composable extras behind Bazel flags: default lean core (RE2, no archive), an extended binary composed from the same tree via bool_flag + config_setting + select() + -DXFF_WITH_* defines, a .bazelrc --config=full convenience, runtime feature-reporting + graceful "not built in" errors, and a NOTICE assembled from the enabled set. Distinct from the #73 --feature runtime gates. Also record the #85 pcre2 finding + plan: pcre2 is in the BCR (upstream-maintained, 10.47), a clean BSD-3-Clause dep (no new license type); add a PCRE2-backed regex::Matcher gated by the //xff:pcre extra, RE2 stays the default, PCRE2 opt-in via -regextype with match/backtrack limits (ReDoS).
helly25
added a commit
that referenced
this pull request
Jul 6, 2026
…time extras (#276) * docs(TODO): record #83 archive decision - libarchive + NOTICE plan Archive diving (--archive) will use libarchive via its BCR module, as a read-only vfs::FileSystem backend (entries as virtual paths, so the predicate/action set incl. -grep works unchanged). Two build variants planned: minimal (tar+gz+bz2) and extended (+xz/zstd/zip). NOTICE obligations are all permissive: net-new types BSD-2-Clause / Zlib / bzip2-1.0.6 / 0BSD; the dual-licensed zstd and mbedtls are pinned to their permissive arms (BSD-3 / Apache-2.0), lz4's library (BSD-2) not its GPL CLI, so no copyleft. Third-party notices maintained + scaled to the enabled codec set. * docs(TODO): composable build-time extras + pcre2 (#85) plan Record the architecture decision that heavy/special libs (libarchive #83, pcre2 #85, future ones) are build-time composable extras behind Bazel flags: default lean core (RE2, no archive), an extended binary composed from the same tree via bool_flag + config_setting + select() + -DXFF_WITH_* defines, a .bazelrc --config=full convenience, runtime feature-reporting + graceful "not built in" errors, and a NOTICE assembled from the enabled set. Distinct from the #73 --feature runtime gates. Also record the #85 pcre2 finding + plan: pcre2 is in the BCR (upstream-maintained, 10.47), a clean BSD-3-Clause dep (no new license type); add a PCRE2-backed regex::Matcher gated by the //xff:pcre extra, RE2 stays the default, PCRE2 opt-in via -regextype with match/backtrack limits (ReDoS).
helly25
added a commit
that referenced
this pull request
Jul 8, 2026
…s to #83/#85 #297 is the code-SOT self-registration license lib (core notices, LICENSE genrule, drift guard). The dual binary (xff_minimal/xff_full via alias, manual), the stub/real xff/archive + xff/pcre modules (@libarchive/@pcre2, self-register their notices), the full-fat NOTICE + full-config drift check, and the CI dual build are deferred to #83/#85 with the agreed design captured. Under self- registration a minimal binary's core-only NOTICE is correct; the extras' notices arrive with their real modules.
helly25
added a commit
that referenced
this pull request
Jul 8, 2026
* feat(license): code-SOT notice lib with self-registration (#115 follow-up) Invert the notice handling per the agreed design: the code is the SOT and the repo files are checked against it (not the reverse). - New xff/license lib: a Notice registry + a Registrar helper. Components self-register - core deps (Abseil/RE2/mbo) from license.cc (always linked); a build-extra will register from its own TU (so it appears exactly when linked). Notices() returns the set sorted by component (deterministic across static-init order). NoticeText() assembles the xff attribution header + the sorted components; this is the SOT for the repo NOTICE. - LICENSE stays canonical: LicenseText() is generated from //:LICENSE via a genrule (byte-exact, no leading-newline artifact) and embedded so a single binary reproduces it. - --help=notice / --help=license dump the compiled-in subset from the lib. Retire #296's files->.cc genrule and xff/cli/notices.*; help_cc now deps //xff/license:license_cc. - license_test guards drift: the committed NOTICE == NoticeText() and LICENSE == LicenseText() (read from runfiles), plus the core deps are present + sorted. (No extra is linked yet, so NoticeText is the full set; #83 makes the compare full-fat once libarchive self-registers.) TODO(license.h): move to C++23 #embed + reproduce each dependency's own license file verbatim (RE2 BSD-3 etc.), not just Apache + the manifest. * chore: spell the author name Boerger (international) not Börger The copyright name lives in the license SOT (NoticeText in xff/license/license.cc) and the committed NOTICE; change both to the international spelling 'Boerger'. * docs(todo): record license self-reg shipped + defer dual-binary/extras to #83/#85 #297 is the code-SOT self-registration license lib (core notices, LICENSE genrule, drift guard). The dual binary (xff_minimal/xff_full via alias, manual), the stub/real xff/archive + xff/pcre modules (@libarchive/@pcre2, self-register their notices), the full-fat NOTICE + full-config drift check, and the CI dual build are deferred to #83/#85 with the agreed design captured. Under self- registration a minimal binary's core-only NOTICE is correct; the extras' notices arrive with their real modules.
helly25
added a commit
that referenced
this pull request
Jul 9, 2026
… Grammar (#85) (#308) Prep for the PCRE2 backend. Matcher no longer wraps RE2 directly: it owns a RegexBackend (backend.h) behind a unique_ptr and forwards each op, and Compile takes a Grammar {kRe2, kPcre2} (default kRe2). The RE2 code moves into a private Re2Backend; the public regex.h no longer includes re2/re2.h. kPcre2 currently returns an Unimplemented error ("PCRE2 not built into this binary") - which is exactly the future lean-build behavior. This backend seam is what the dual binary will gate on (lean = RE2 only + PCRE2 stub, full = real PCRE2) and what the runtime -regextype selection dispatches through, with no API change either way. No behavior change: kRe2 is the default, all existing Matcher callers are unaffected (Compile's third arg is defaulted). Test: regex_test gains the explicit-kRe2 default and the kPcre2 Unimplemented-stub cases; full //xff/... suite green (the regex.h header change touches nothing else).
helly25
added a commit
that referenced
this pull request
Jul 9, 2026
The self-registration seam for the composable-extras architecture (mirrors the license Notice registrars). backend.h gains a Pcre2Factory typedef, a RegisterPcre2Backend entry point, and a Pcre2Registrar for static-init self-registration. regex.cc holds the single factory slot (a Meyers static, so a registrar in another TU can write it during static init); Compile(kPcre2) now consults the slot -- registered (full build) -> real backend, unregistered (lean build) -> the same Unimplemented "not built in" error, never a silent RE2 fallback. regex.h exposes Pcre2Available() for the help presence line. Linkage is presence: the real PCRE2 backend (built only into the full binary, from its own removable target under third_party/pcre2) self-registers via a Pcre2Registrar; a lean build links no such TU, so nothing registers. No behavior change in this (lean) build: nothing is registered, so Pcre2Available() is false and -regextype=pcre still errors. Test: regex_test asserts Pcre2Available() is false and Compile(kPcre2) is Unimplemented with no backend registered.
helly25
added a commit
that referenced
this pull request
Jul 9, 2026
…pointer (#85) (#310) A std::function is the idiomatic type-erased callable: it also accepts capturing lambdas and functors, not just plain functions, so the future third_party/pcre2 registrant is not constrained to a free function. The factory is invoked once per pattern compile, so type-erasure cost is irrelevant. The slot becomes a default- empty std::function (moved on register), Compile(kPcre2) tests it with !factory, and Pcre2Available() is static_cast<bool>. Test: regex_test unchanged and green (Pcre2Available() false / kPcre2 Unimplemented with nothing registered).
helly25
added a commit
that referenced
this pull request
Jul 9, 2026
…ilt-in error (#85) (#311) --regextype now accepts PCRE2 alongside RE2/EXACT. Because PCRE2 is a build-time extra, ResolveGrepLiteral (which runs unconditionally, so it guards the whole run, not just -grep) errors when --regextype=PCRE2 is used on a binary that does not link the backend (regex::Pcre2Available() is false) - a clean usage error (exit 2), never a silent RE2 fallback. MATCH stays reserved; unknown values still error. The actual grammar threading to the compile sites lands with the real backend (the threading is dead code until a backend registers, and Pcre2Available() is only true once that ships). --regextype display/summary updated to RE2|PCRE2|EXACT. Also records two design decisions in TODO.md: #83 archive-mode scope (recurse into any archive found, uniform archive==directory, opt-in, read-only) and #121 (a future shell-glob third grammar on the RegexBackend abstraction). Test: grep_test + run_test assert --regextype=PCRE2 is a usage error 'not built into this binary' on this lean build; MATCH stays reserved; RE2/EXACT unchanged.
helly25
added a commit
that referenced
this pull request
Jul 9, 2026
full_binary_test data-deps xff_full, so leaving it non-manual meant `bazel test //...` built xff_full anyway - defeating xff_full's `manual` tag (and, once #85 links pcre2, it would drag the extras into the lean cells). Anything depending on the manual full binary must be manual too. It now runs explicitly (`bazel test //xff/cli:full_binary_test`, verified green) or in the full CI cell; the argv[0] `_full`-strip stays covered in default CI by config_test.
helly25
added a commit
that referenced
this pull request
Jul 10, 2026
…flag (#85, #115) (#312) * Add composable-extras scaffolding: xff_full dual binary + //xff:pcre flag (#85, #115) The PCRE2 backend (#85) and archive diving (#83) are composable build-time extras: the stock binary stays a lean core, and a full binary linking the extras is built from the same tree. This lands the Bazel + argv[0] scaffolding (the real PCRE2 backend follows in the next PR). - //xff:pcre bool_flag + :pcre_enabled config_setting (mirrors //xff:archive), default off, so an extra links only when turned on. - Dual binary in //xff/cli: `xff` (lean, what //... and every test builds) and `xff_full` (tags=["manual"], same core + a select() on its deps for the extra backends). No alias: an alias's runfile takes the resolved target's basename, which would break every bashtest's hardcoded xff/cli/xff lookup; two named cc_binary targets keep the `xff` artifact named `xff` (zero test churn) and let the user pick which binary to run. - .bazelrc `--config=full` turns the extras on; `--config=full --//xff:pcre=false` drops one from an otherwise-full build. - DefaultStyleForProgram strips a trailing `_full` suffix, so `xff_full` resolves to the xff style (and `find_full` -> find, `rg_full` -> rg) rather than falling through to the xff default and silently losing the base semantics. - full_binary_test.sh drives the real xff_full: it resolves to the xff style (accepts the xff-only -grep) and, without the PCRE2 extra linked, still rejects --regextype=PCRE2 with a usage error. Realigns the TODO.md extras section (define/#ifdef -> self-registration; the alias sketch -> the shipped two-binary model) and marks #115 scaffolding done. * Prefix extras build flags with xff_; share main() via a cc_library Two follow-ups on the composable-extras scaffolding, folded into the same PR: - Prefix the build-setting flags with the `xff_` package prefix, matching the library-flag convention in STYLE_CPP.md: `//xff:archive`/`archive_enabled` -> `//xff:xff_archive`/`xff_archive_enabled`, and the new pcre pair likewise (`//xff:xff_pcre`/`xff_pcre_enabled`). Updates .bazelrc + all comments/docs. - Replace the `_XFF_CORE_DEPS` Starlark list with a real `//xff/cli:main_cc` cc_library holding main.cc + the core deps; `xff` and `xff_full` are now thin binaries over it (no srcs of their own). main.cc compiles once, neither binary carries its own main(), and xff_full differs only by the extras it adds - so they cannot drift. Verified main() links from the library and self-registration (license notices) still fires through it. * Tag full_binary_test manual (it depends on the manual xff_full) full_binary_test data-deps xff_full, so leaving it non-manual meant `bazel test //...` built xff_full anyway - defeating xff_full's `manual` tag (and, once #85 links pcre2, it would drag the extras into the lean cells). Anything depending on the manual full binary must be manual too. It now runs explicitly (`bazel test //xff/cli:full_binary_test`, verified green) or in the full CI cell; the argv[0] `_full`-strip stays covered in default CI by config_test. * Rename the extras bazel config full -> xff_full Match the config name to the binary it builds, and carry the xff_ prefix consistently with the flags: `bazel build --config=xff_full //xff/cli:xff_full`. Updates .bazelrc (common:xff_full) and every --config=full / build:full reference in the BUILD files, TODO.md, and full_binary_test.sh.
helly25
added a commit
that referenced
this pull request
Jul 10, 2026
…hers (#85) (#313) Resolve the regex grammar once from --regextype at parse time and carry it on Command.grammar, so every pattern matcher (-regex/-iregex/-rxc/-irxc/-grep and the -capture extraction regex) compiles under it, and the ApplyCaseMode case-insensitive recompile reuses it. Previously the parser always compiled RE2 regardless of --regextype. - parser: GrammarFromGlobals maps --regextype (PCRE2 -> kPcre2, else kRe2, last wins); ExprParser carries grammar_ and threads it through MakePredicate -> CompileNodeRegex -> Matcher::Compile; ApplyCaseModeToNode takes the grammar too. - ast: Command gains a `grammar` field (default kRe2). This is the plumbing for #85's PCRE2 backend. It is safe to land before the backend: with no backend linked, Matcher::Compile(kPcre2) is Unimplemented and run.cc's ResolveGrepLiteral already rejects --regextype=PCRE2 (exit 2) before the walk, so the grammar is RE2 in every reachable path today. GrammarFromGlobals is deliberately lenient (unknown/unavailable -> RE2); ResolveGrepLiteral stays the single validating reader. parser_test asserts Command.grammar tracks --regextype.
helly25
added a commit
that referenced
this pull request
Jul 10, 2026
… literal path (#85) (#314) EXACT becomes a first-class match engine (Grammar::kExact) behind the RegexBackend seam, not a -grep-only bool - the first non-RE2 engine, proving the multi-engine architecture with a trivial core (always-linked, no dependency) backend. - regex: ExactBackend implements RegexBackend via std::string comparison - FullMatch = equality, PartialMatch = substring, FindFirst = first occurrence span, Rewrite = literal find/replace (no backreferences); case_insensitive folds ASCII case on both sides. Compile(kExact) never fails (no pattern to compile). - parser: GrammarFromGlobals maps --regextype=EXACT -> kExact, so EXACT now reaches every pattern predicate (-regex/-rxc/-grep), not just -grep. - engine: unify -grep. The old ctx.grep_literal StrContains path is exactly what ExactBackend.PartialMatch/FindFirst do, so -grep now runs through the pre-compiled matcher like -rxc; drop the grep_literal EvalContext field. Behavior is preserved (the existing EXACT -grep test still passes) and EXACT now respects case mode uniformly (the old StrContains path ignored -i/smart-case). - run: ResolveGrepLiteral (returned the literal bool) becomes ValidateRegextype (validates the selector only; PCRE2-not-built / MATCH-reserved / unknown are still usage errors before the walk). The grammar is resolved by the parser. - help: --regextype summary reframed as "match engine: RE2 / EXACT / PCRE2". Tests: regex_test (kExact ops + ci + literal span), parser_test (EXACT -> kExact), evaluate_test (-grep and -rxc under EXACT). bazel test //xff/... -> 72/72.
helly25
added a commit
that referenced
this pull request
Jul 10, 2026
#121) (#315) FnmatchBackend is the second core engine behind the RegexBackend seam: a flat POSIX shell wildcard (`*`/`?`/`[...]`, where `*` matches any character including `/` - no FNM_PATHNAME, i.e. the -name/-path matching offered as a --regextype). - regex: Grammar::kFnmatch + FnmatchBackend. fnmatch is a whole-string test, so FullMatch runs it anchored; PartialMatch wraps the pattern in `*...*` to match anywhere (`**` collapses to `*` in POSIX fnmatch, so the always-wrap is safe); FindFirst reports the whole text as the span (no sub-span in fnmatch); FullMatchCaptures is the whole match only; Rewrite is a no-op (a glob has no rewrite semantics). case_insensitive sets FNM_CASEFOLD (the _GNU_SOURCE guard mirrors the evaluator). No pattern to compile, so Compile(kFnmatch) never fails. - parser: GrammarFromGlobals maps --regextype=FNMATCH -> kFnmatch, so it reaches every pattern predicate (-regex/-rxc/-grep). - run: ValidateRegextype accepts FNMATCH (a core engine, always available). - help: --regextype summary lists FNMATCH (glob). Tests: regex_test (whole-string wildcard, `*...*` PartialMatch + whole-text span, FNM_CASEFOLD), parser_test (FNMATCH -> kFnmatch), evaluate_test (-rxc under FNMATCH). bazel test //xff/... -> 72/72.
helly25
added a commit
that referenced
this pull request
Jul 10, 2026
#85, #121) (#316) GLOB is the path-segment-aware shell glob (`*`/`?` stop at `/`, `**` crosses directories - the shell / gitignore semantics), the fourth core engine behind the RegexBackend seam. It reuses the escape-aware glob->RE2 translation already in the gitignore engine, now factored into a shared user-lib. - new //xff/glob: `glob::GlobToRegex(pattern)` translates a shell glob to an RE2 pattern (`*`->`[^/]*`, `?`->`[^/]`, `**` segment->cross-directory, `[!]`->`[^]`, RE2 metacharacters + `\`-escapes handled). Extracted verbatim from ignore.cc. - ignore: now calls glob::GlobToRegex (behavior unchanged; ignore_test still green). - regex: Grammar::kGlob compiles the translated pattern as RE2 and reuses Re2Backend, so FullMatch/PartialMatch/FindFirst (a real span)/Rewrite all come from RE2 - no fnmatch whole-string limitation. A shared `compile_re2` lambda backs kRe2 and kGlob. - parser/run/help: --regextype=GLOB -> kGlob (reaches -regex/-rxc/-grep); a core engine, always available; the summary lists all five engines. Tests: glob_test (the translation), regex_test (path-aware `*`/`**`, RE2-backed span + partial), parser_test (GLOB -> kGlob), evaluate_test (-rxc path-aware). //xff/... -> 73/73.
helly25
added a commit
that referenced
this pull request
Jul 10, 2026
…#85) (#317) The last regextype engine and the first composable build extra: a real PCRE2 backend that only //xff/cli:xff_full links, completing #85. - third_party/pcre2/ (removable dir): Pcre2Backend implements xff/regex's RegexBackend via the PCRE2 C API (compile / match / ovector / substitute). FullMatch is ANCHORED|ENDANCHORED (whole-string); FindFirst/captures read the ovector; Rewrite translates the RE2 `\1` contract to PCRE2 `$1`; case_insensitive is PCRE2_CASELESS. ReDoS-guarded by a match + depth limit on the match context. Each match allocates its own match_data, so a shared const backend is thread-safe. - Self-registration (alwayslink), exactly like the core engines: a Pcre2Registrar hands the factory to xff/regex (Pcre2Available() flips true, Matcher::Compile(kPcre2) works), and a license::Registrar adds the BSD-3 notice. The core never references PCRE2. - MODULE.bazel: bazel_dep(pcre2 10.47). Linked into xff_full via select({"//xff:xff_pcre_enabled": ["//third_party/pcre2:pcre2_backend"]}); both the backend lib and its test are `manual`, so a plain //... build never fetches @pcre2. - backend.h becomes an exported header of //xff/regex (the extension seam), and //xff/regex + //xff/license are now visible to //third_party. - CI: a `full` cell runs the whole suite + the manual full targets under --config=xff_full (the only cell that links the extras), wired into the `done` gate. Tests: pcre2_backend_test (unit - backreferences, lookahead, captures, RE2-style rewrite, anchoring, case-fold, invalid-pattern), and full_binary_test is now config-aware (asserts PCRE2 works when linked, errors when not). Lean //... stays 73/73 and never touches @pcre2; --config=xff_full is 75/75.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Converts
FormatPrintf's twoswitchstatements to constexpr dispatch tables, per the TODO that switch/case lists be alphabetical and constexpr-dispatched where possible.What
kPrintfEscapes:constexpr LimitedMap<char, char>for the\escapes (\n/\t/\r/\0/\\).kPrintfDirectives:constexpr LimitedMap<char, PrintfDirective>for the%directives, wherePrintfDirective = void(*)(std::string&, const Visit&)and each entry is a captureless lambda. Keyed alphabetically, looked up with.find()- the same pattern as the enginekDispatchfor primaries.FormatPrintfnow looks up both tables; unknown directives/escapes still emit literally.Behavior
Unchanged - the existing
-printftests (directives, escapes, owner directives) all pass on default +--config=clang --config=asan(12/12). clang-format + no-em-dash gates clean.MatchesType/MatchesSize/TypeLetterare the same kind of char-keyed switch and are candidates for a follow-up sweep.