Repository navigation
Description
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
-
Callrows per language with every alias id (csharp_invocation_expr_kinds!folds in; TS/TSX list all four ids with a row comment that three are grammar-aliased productions, per fix(checker): C-family is_call never matches — calls are the aliased kind #1254's drift-marker technique); Perl's bareword call istext_gated_by perl_bareword_is_call(fix(checker/perl): is_call matches bareword keys and double-reports calls #1580); ElixirCallis an owner forCallandtext_gated_byforFuncOpener— two rows, one kind, which the census permits across roles. -
StringLitrows: the currentimpl_simple_is_string!/impl_js_family_is_string!arguments, with interpolation child kinds as awrapsattribute sostring_operand_typereads its set from the row; type-keyword aliases (String2in TS, PHP'sString2)Excluded(StringLit, "type keyword")per fix(getter/typescript): the string type keyword counts as operator and operand #1261/fix(checker/php): is_string matches thestringtype keyword #1474. -
is_call_with_code,is_string_with_code, the getter arms and thefind/countfilters read the rows.alterator.rsreadsowns_StringLitfor its string arm and keeps its non-string flattening list and the Tclkeeps_childrenveto as they are, with a comment saying which half is shared.
Tests and edge cases
-
big-code-analysis-ast/src/checker.rs'sassert_variants_is_string/assert_no_string_matchestests 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.rsunchanged and passing;bca dumpoutput on the corpus byte-identical before and after (the alterator shapes the dump). -
bca find -t string/-t callandbca counton 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/countcorpus diff;bca opsdiff; snapshot counts;BCA_GATE:line.
Activity
dekobon commentedon Oct 23, 2026
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.
dekobon commentedon Oct 25, 2026
Done —
CallandStringLitdeclared in every language;is_call/is_string, the getters' interpolation sets, the dump's string flattening and the ABC branch count read the rowsBranch
refactor/1696-1698-recognition-rolesfrom7425561f(main), commitsc168daae(R08) andf58ade41(the C alterator arm the scripted pass missed); ten R06 commits before them. Not pushed, no PR.Rows.
CallandStringLitin all 25 languages (roles/<lang>.rs), with shared fragmentsc_family_calls!/c_family_strings!,js_family_strings!,tcl_family_strings!(+tcl_family_string_gates!, emitted per dialect over itsBracedWordKinds). The two gates the issue named aregated_byentries: Perl'sCallExpressionWithBareword(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'sBracedWord(gated_by is_braced_literal)(=!Getter::is_braced_script_word, #1381/#1382).wrapsholds each getter's former interpolation set — PHP's direct-child kinds and{, Ruby's array element kinds, Perl'slist_item, Groovy's two GString forms, the Tcl family's substitutions.excludes: PHPString2, TSString2, TSXString3(type keywords, #1261/#1474). Every call-site rationale from the siximpl_simple_is_string!/impl_js_family_is_string!sites and the per-languageis_callcomments 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 unsuffixedCallExpression(#1254); TypeScript's and TSX'sCallExpression2..4— a decorator call, atypeof f()in a type and a generic call all arrive as the first id; C#'sInvocationExpression/InvocationExpression2— bare, member, generic and nullsafe calls in a class, struct, record, interface and field initialiser all carry the third; Ruby'sCall/Call2/Call4— ten call spellings all carryCall3,Call5is the hidden_call; the JS family's namedString— every literal, a module string, a property key and an import alias included, is theString2alias; PHP's hiddenString3; 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_stringand the_with_codepair come fromimpl_call_row!/impl_string_lit_row!(two macros: the bypass gate's row-forwarding recogniser binds one kind to one row). The_with_codespellings are the rows under their gates; the byte-less ones are the row's kind table through a newlists_kind— not a known-empty chain, which here would read a Tcl body as a script, and the existingtcl_script_body_is_a_string_only_to_the_byteless_spellingpins that the two still disagree. Perl'sis_call_with_codeand the Tcl family'sis_string_with_codeoverrides are gone. Every getter'sstring_operand_type(node, &[…])readsstring_lit::WRAPS; Bash's expansion read, PHP's direct-child set (theheredoc_bodydescent stays) and Kotlin's long form read it too (Kotlin's short$namehas no node and stays a byte read). The dump's string flattening reads the row's owners through a sharedAlterator::flatten_verbatim; the non-string flattening — char / rune literals, Groovy's fragments, PHP'sstringkeyword alias with its #1474 / grammar-dispatch §6 rationale — stays each language's own arm beside it, and the Tcl family'skeeps_childrenveto is untouched;tests/grammars/alterator_string_flattening.rsis unchanged and passes. The ABC branch count readscall::owns_kindin every language, keeping object construction as the walker's own (NewExpression,ObjectCreationExpression,ConstructorInitializer,ConstructorDelegationCall, Groovy'sNew, Java'sNew/ExplicitConstructorInvocation, Rust'sTryExpression). 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 innexits(Go, Lua, Bash, iRules, Perl, Elixir), Elixir's cognitive / cyclomatic keyword dispatch oncall, Perl ABC's argument-shape helpers, Go's boolean-slot leaf, Ruby's symbol-name readers, Java ABC's return / lambda slots, Python'syieldoperator. Bypass baseline 40 → 10, all Elixir's documented byte-less kind table (#275) andimpl_exit_match_kinds!(R07'sExit). Census 314 → 274, coverage −140, every--updateremove-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 thename =>/-keyhash keys (the latter the file-test misparse of #1545/#1572), the Tcl family'sproc/if/whenbodies against braced values, PHP'sstringkeyword and(string)cast, Bash's$"…"in argument and assignment position (#1358), Ruby's safe-navigation call and%w/%ielements.elixir_anonymous_function_contributes_lambda_nestingandc_string_and_concatenated_string_flattenedwere 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/countplus 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, awrapsentry dropped, PHP's keyword moved intoowns— which the DSL refuses to build) and 12 consumers severed.make pre-commit:BCA_GATE: pass;make chain-auditpasses;make bench-scaling87 of 87; single-feature builds (rust / go / elixir / tcl / kotlin lib, rust / tcl / perl ast) green;check-test-lang-gates-compare COMPARE_REF=origin/mainloses no build; both snapshot forms clean; the output submodule untouched.Review (astra-review, fresh context, checkpoint 4 on R08 and on the combined
7425561f..HEADdiff): 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_stringare 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 awrapsentry (no node). PHP'sString2isexcludedby the row and listed by the dump arm, for the reasons on both.dekobon commentedon Oct 26, 2026
Post-landing review pass (fixes in
18ee3c98)Two corrections to what this issue's close-out claimed, found by the acceptance-criteria audit:
StringLitrows were stillpending R08and their checkers still readimpl_simple_is_string!. Both now declareStringLit { owns [StringLiteral, RawStringLiteral] }— the literals the pass must not strip — and readimpl_row_predicates!;impl_simple_is_string!is gone, and the census / coverage baselines drop their lastR08cells and the four ids.find -t stringon both helper grammars is byte-identical to before.gated_by bareword_is_call, nottext_gated_byas step 1 says: the gate reads the parent off the chain and no bytes, sogated_byis the correct keyword. The byte-lessis_callis the row's kind table either way.Also from the same pass: every language's
is_primitivenow reads thePrimitiverow's kind table, with the text-key selection for C# modifiers and Kotlin'sreifiedmoved intokeys_operator_by_textoverrides (they were still answered byis_primitiveunder 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.refactor(checker,getter,alterator,abc): declare Call and StringLit by…
refactor(roles,metrics,checker): take the post-landing review passes …