Skip to content

refactor(checker,getter): declare Call and StringLit; fold the alterator and find/count consumers #1698

Closed

Description

@dekobon

Objective

Declare Call and StringLit per language and route Checker::is_call, is_string, the getter's call/string arms, alterator.rs's string flattening and the find/count filters through them. Zero drift.

Umbrella: R00: #1690 · Planning ID: R08: #1698
Completion prerequisites: R04: #1694 Completion of R04: #1694 includes a recorded go decision.

Why this exists

These are the two roles lesson 35 was written about: is_string and get_op_type disagreed on TypeScript String2 (#313), and were later resolved the other way in TS (#1261) and PHP (#1474) — four issues to make two predicates agree on one kind, per language. is_call is narrower than ABC's branches by convention in thirteen languages (#1456 recorded the divergence rather than fixing it), and the TS/TSX is_call matches one of four call_expression ids where C/C++/ObjC/Ruby/C# list every alias of theirs — latent at the pinned grammar (bca dump on decorator, typeof-query and type-position fixtures emits only the unsuffixed id), but exactly the #1254 shape that a grammar bump turns live.

alterator.rs is the third encoding of StringLit: it flattens the string kinds is_string matches by its own list, plus kinds it deliberately flattens that are not strings (C++ CharLiteral, alterator.rs), and for Tcl it must keep a braced word's children when the word is a script (keeps_children). The shared part is the string membership; the non-string flattening and the contextual veto are the alterator's own and stay, and parser.rs's find/count filters are the consumers of is_call_with_code/is_string_with_code that #1580 made contextual.

Implementation steps

Tests and edge cases

  • big-code-analysis-ast/src/checker.rs's assert_variants_is_string / assert_no_string_matches tests unchanged and passing; the fix(checker): C-family is_call never matches — calls are the aliased kind #1254 alias drift markers kept.
  • tests/grammars/alterator_string_flattening.rs unchanged and passing; bca dump output on the corpus byte-identical before and after (the alterator shapes the dump).
  • bca find -t string / -t call and bca count on the corpus before and after: identical.
  • Zero drift on bca ops, metrics, spaces; both snapshot forms; plain run.

Acceptance criteria

  • No call or string kind matched inside the boundary outside a row; bypass gate clean.
  • The alterator's "so as not to diverge from is_string" comment is replaced by the row reference for the string half only.

Verification and completion evidence

  • find/count corpus diff; bca ops diff; snapshot counts; BCA_GATE: line.

Activity

  1. added
    refactorCode maintainability / tech-debt cleanup (no behaviour change)
    on Oct 23, 2026
  2. dekobon commented on Oct 23, 2026

    @dekobon
    OwnerAuthor

    Tracking is ready

    The body now links the master issue and the work packages it references. Native parent and blocked-by relationships match the listed completion prerequisites. Implementation and acceptance checkboxes remain open; prerequisite closure and the R04: #1694 go decision govern execution order.

  3. dekobon commented on Oct 25, 2026

    @dekobon
    OwnerAuthor

    Done — Call and StringLit declared in every language; is_call / is_string, the getters' interpolation sets, the dump's string flattening and the ABC branch count read the rows

    Branch refactor/1696-1698-recognition-roles from 7425561f (main), commits c168daae (R08) and f58ade41 (the C alterator arm the scripted pass missed); ten R06 commits before them. Not pushed, no PR.

    Rows. Call and StringLit in all 25 languages (roles/<lang>.rs), with shared fragments c_family_calls! / c_family_strings!, js_family_strings!, tcl_family_strings! (+ tcl_family_string_gates!, emitted per dialect over its BracedWordKinds). The two gates the issue named are gated_by entries: Perl's CallExpressionWithBareword(gated_by bareword_is_call) (= perl_bareword_is_call, the outermost-dispatch and not-a-hash-key rule, derivation on the row) and the Tcl family's BracedWord(gated_by is_braced_literal) (= !Getter::is_braced_script_word, #1381/#1382). wraps holds each getter's former interpolation set — PHP's direct-child kinds and {, Ruby's array element kinds, Perl's list_item, Groovy's two GString forms, the Tcl family's substitutions. excludes: PHP String2, TS String2, TSX String3 (type keywords, #1261/#1474). Every call-site rationale from the six impl_simple_is_string! / impl_js_family_is_string! sites and the per-language is_call comments moved onto the rows (macro-comments rule).

    What the rows now record about the pinned grammars (each probed with bca dump, each a defensive owner the tests hold unseen): C/C++/Mozcpp/ObjC's unsuffixed CallExpression (#1254); TypeScript's and TSX's CallExpression2..4 — a decorator call, a typeof f() in a type and a generic call all arrive as the first id; C#'s InvocationExpression / InvocationExpression2 — bare, member, generic and nullsafe calls in a class, struct, record, interface and field initialiser all carry the third; Ruby's Call / Call2 / Call4 — ten call spellings all carry Call3, Call5 is the hidden _call; the JS family's named String — every literal, a module string, a property key and an import alias included, is the String2 alias; PHP's hidden String3; Bash's three heredoc aliases. The issue's "csharp_invocation_expr_kinds! folds in" is done by listing the three ids on the row; the macro's one remaining user is a comment.

    Consumers. Checker::is_call / is_string and the _with_code pair come from impl_call_row! / impl_string_lit_row! (two macros: the bypass gate's row-forwarding recogniser binds one kind to one row). The _with_code spellings are the rows under their gates; the byte-less ones are the row's kind table through a new lists_kind — not a known-empty chain, which here would read a Tcl body as a script, and the existing tcl_script_body_is_a_string_only_to_the_byteless_spelling pins that the two still disagree. Perl's is_call_with_code and the Tcl family's is_string_with_code overrides are gone. Every getter's string_operand_type(node, &[…]) reads string_lit::WRAPS; Bash's expansion read, PHP's direct-child set (the heredoc_body descent stays) and Kotlin's long form read it too (Kotlin's short $name has no node and stays a byte read). The dump's string flattening reads the row's owners through a shared Alterator::flatten_verbatim; the non-string flattening — char / rune literals, Groovy's fragments, PHP's string keyword alias with its #1474 / grammar-dispatch §6 rationale — stays each language's own arm beside it, and the Tcl family's keeps_children veto is untouched; tests/grammars/alterator_string_flattening.rs is unchanged and passes. The ABC branch count reads call::owns_kind in every language, keeping object construction as the walker's own (NewExpression, ObjectCreationExpression, ConstructorInitializer, ConstructorDelegationCall, Groovy's New, Java's New / ExplicitConstructorInvocation, Rust's TryExpression). Java's and Elixir's cognitive boundary / lambda arms read their rows as the other languages' do since R06.

    Exemptions, each naming the role and the question it is not: the operand classification of string kinds in every getter (a projection the row does not answer), PHP's { / variable-name interpolation reads, get_operator_id_as_str's {} spelling, the exit-name reads on call kinds in nexits (Go, Lua, Bash, iRules, Perl, Elixir), Elixir's cognitive / cyclomatic keyword dispatch on call, Perl ABC's argument-shape helpers, Go's boolean-slot leaf, Ruby's symbol-name readers, Java ABC's return / lambda slots, Python's yield operator. Bypass baseline 40 → 10, all Elixir's documented byte-less kind table (#275) and impl_exit_match_kinds! (R07's Exit). Census 314 → 274, coverage −140, every --update remove-only.

    Tests. roles/call_string_tests.rs: per language, (is_call_with_code, is_string_with_code) of every call / string node by text in document order — every other node asserted neither — with every owner covered and the defensive aliases asserted unseen. Fixtures spell the gated cases: Perl's nested callees and the name => / -key hash keys (the latter the file-test misparse of #1545/#1572), the Tcl family's proc / if / when bodies against braced values, PHP's string keyword and (string) cast, Bash's $"…" in argument and assignment position (#1358), Ruby's safe-navigation call and %w / %i elements. elixir_anonymous_function_contributes_lambda_nesting and c_string_and_concatenated_string_flattened were added where a perturbation found a consumer unguarded.

    Evidence. Zero drift against the base binary: 25 populations, CLI ops / metrics / dump / find -t call|string|function / count plus the library-level verbatim record (raw bytes, Alterator export, find, count): 0 of 26,992 outputs differ. Recovery sweep over every fixture (each line, 16 offsets, after every operator punctuation, every word boundary; CLI + verbatim): 129,042 outputs, 0 differing, 0 failed executions. Perturbations, each failing at least one named test: 25 row edits (a gate dropped, an emitted alias dropped, a defensive alias dropped, a wraps entry dropped, PHP's keyword moved into owns — which the DSL refuses to build) and 12 consumers severed. make pre-commit: BCA_GATE: pass; make chain-audit passes; make bench-scaling 87 of 87; single-feature builds (rust / go / elixir / tcl / kotlin lib, rust / tcl / perl ast) green; check-test-lang-gates-compare COMPARE_REF=origin/main loses no build; both snapshot forms clean; the output submodule untouched.

    Review (astra-review, fresh context, checkpoint 4 on R08 and on the combined 7425561f..HEAD diff): no actionable findings; the reviewer independently ran both library suites (4,411 passed), the role tests and the three role gates. One thing the review did not catch and the issue's own checklist did: the C alterator arm was left unmigrated by the scripted pass (it had no test of its own — the C++ tests cover the C++ arm), found when ticking "the alterator reads the row for its string half"; migrated, tested, re-measured (0 drift on the C-family populations).

    Limitations. The byte-less is_call / is_string are the kind tables and say so; the three gated rows (Perl bareword, Tcl / iRules braced word) are the only places the two spellings differ, each pinned. Kotlin's short-form interpolation cannot be a wraps entry (no node). PHP's String2 is excluded by the row and listed by the dump arm, for the reasons on both.

  4. dekobon commented on Oct 26, 2026

    @dekobon
    OwnerAuthor

    Post-landing review pass (fixes in 18ee3c98)

    Two corrections to what this issue's close-out claimed, found by the acceptance-criteria audit:

    • "Declared in all twenty-five languages" was two short. Ccomment's and Preproc's StringLit rows were still pending R08 and their checkers still read impl_simple_is_string!. Both now declare StringLit { owns [StringLiteral, RawStringLiteral] } — the literals the pass must not strip — and read impl_row_predicates!; impl_simple_is_string! is gone, and the census / coverage baselines drop their last R08 cells and the four ids. find -t string on both helper grammars is byte-identical to before.
    • Perl's bareword call is gated_by bareword_is_call, not text_gated_by as step 1 says: the gate reads the parent off the chain and no bytes, so gated_by is the correct keyword. The byte-less is_call is the row's kind table either way.

    Also from the same pass: every language's is_primitive now reads the Primitive row's kind table, with the text-key selection for C# modifiers and Kotlin's reified moved into keys_operator_by_text overrides (they were still answered by is_primitive under the precondition #1599 deleted). Evidence on HEAD: 0 differing of 27,008 corpus records compared, 0 of 124,488 recovery records; the counts in the earlier comments ("26,992") were a miscount of the same 27,024-record runs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    refactorCode maintainability / tech-debt cleanup (no behaviour change)

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions