Validate continuation evidence, sum per-turn tokens and turns, retain final cumulative cost, and include background wait in elapsed time. Add an offline pinned-CLI canary before paid evaluation and allow 60-minute sessions within the existing deadline and cleanup reserve.
The ratio guard divided by a 15 ms floor on the small run, so shapes whose
small run took 1 to 2 ms (comments before the naming argument) could hide
a quadratic regression that cost less than 150 ms at 8,000 arguments. The
small size now doubles from 2,000 until its fastest run lasts 20 ms, and
the large run stays four times that size, so the ratio is taken between
runs long enough to measure. The largest shape runs for about 2.4 s.
The guide documents ambiguous_reference for an unqualified call into
another file and a refusal for R6 methods, but no fixture covered
either. Add both. The R6 method is refused by the provider as a class
member (unsupported_occurrence); the guide wrongly listed it among the
shared engine's stale_graph refusals, which now name only dotted
functions and S4 classes.
Member ids carry their class since fc32e539, so the two greet methods of
Parent and Child in s4_and_r6.R are two nodes instead of one. The
owner-absence test sits outside the R test set script and kept the old
count of 22.
The header said the suite hard-fails on CI like the other worker-backed
suites. Only object-literal-owner-resolution shares that tripwire; the
others skip silently when dist/ is missing. The header now says so, and
that the integration tier builds dist/ first.
The guide said 18-provider registry. The providers table in
languages/index.ts has 19 entries with R, one per SupportedLanguages
value (counted from the built registry: Object.keys(providers).length).
The warning for a language whose grammar could not be loaded told the
user to run npm rebuild tree-sitter-<language>. R's grammar is published
as @eagleoutice/tree-sitter-r, so the hint named a package that does not
exist. The grammar loader now keeps a small map of grammars published
under another name, and every other language gets the same hint as
before.
R skips a binding that is not a function when it looks up the callee of
f(x), but the rename provider claimed every parameter, so a call under
function(x, helper = 2) was left unrenamed and failed after the rename.
A parameter whose default is a constant and which the body never assigns
is no longer claimed, so the call is renamed with the function it
reaches. A parameter that may hold a function (a function or computed
default, or an assignment to it in the body) is claimed as before, and
a call of the name under it is refused as ambiguous_reference, since it
may reach either. A parameter without a default that is called stays a
function-valued parameter and its call is left alone.
A <<- inside a function is a global re-definition that the scope graph
keeps as a second declaration of the same name. A rename dropped the
selected function and its calls but left the superassignment, so after
the rename the function wrote a new global of the old name instead of
replacing the renamed one. The top-level form was already refused by
the engine; checkSafety now refuses any <<- or ->> to the name, in or
out of a function, with the same ambiguous_reference code.
The guard looked only at the text before the first dot, so
to.json.foo (a method of the dotted generic to.json) passed, and base
generics such as print, format, summary and plot were never checked
unless the sources defined them. Every dot-separated prefix of the new
name is now tested against the functions the indexed sources define and
a small list of base generics.
A function written in backticks keeps them in its binding key and on its
node, while NAMESPACE entries and importFrom() carry the bare symbol, so
importFrom(pkg, `%||%`) found no defining file and the call degraded to
the name-based guess, which cannot tell two packages' `%||%` apart.
rFileTopLevel now keys the top-level names by symbol (rBareName) and
records the spellings it saw. resolveRNamedImport looks files up by
symbol, populateRNamespaceImports synthesises the import with the
definition's own spelling (one per spelling the provider uses) and lets
a backtick-defined function of the caller package mask its import. The
global-name-fallback veto and the pkg::name() lookups compare symbols.
The dotted-tail guard still compares the name as written, which is how
finalize indexes it. Names without backticks answer exactly as before,
pinned by a characterisation snapshot recorded before the change.
A call and its definition must spell a backticked name alike to be linked,
and importFrom() of a backtick-defined function does not bind. NAMESPACE
exports and rename match the symbol either way.
The method patterns required `A <- setRefClass(...)`, so a bare
`setRefClass("A", methods = list(...))` produced the Class and its fields
but no Method. The method extractor and the class owner lookup never needed
the assignment; match the call itself in both queries.
rMergeBindings sorted same-tier bindings with localeCompare, whose order
follows the machine's locale. Compare node ids by code unit, as the package
discovery does.
expandRWildcardNames scanned every parsed file with find() for each
library() edge (450,000 edges over 3,000 files took 5.9 s). Build the
module-scope lookup once per pass, as the C, C++ and Python providers do.
The grammar parses NA, NA_integer_, NA_real_, NA_character_ and NA_complex_
as `na` nodes, not identifiers, so the identifier branch never ran and no
type was inferred. Type them as integer, numeric, character and complex, and
plain NA as logical.
A UTF-8 byte-order mark before `Package:` hid the package, so its qualified
calls were dropped as external. And `\s*` crossed a newline, so an empty
`Package:` field captured the next line's `Version:` as the package name.
Strip the mark and keep the value on the field's own line.
[[:constructor:]], [[:toString:]] and [[:__proto__:]] resolved on the
prototype chain and spliced function source text into the pattern, so the
pattern compiled to garbage instead of being rejected as an unknown class
and reported as dropped. Look classes up as own properties.
An unterminated `{` after an `if` scanned to the end of the file, and the
resynchronisation then landed inside the region just scanned, so a file of
`if (a) {` lines with no `}` was quadratic (300 KB took 8.8 s, and the file is
parsed several times per analysis). Index where every block ends in one pass
and answer each block from the index. Parsing results are unchanged.
The pattern /^`+|`+$/g is quadratic on an entry such as x, a long run of
backticks, then y; the entry is repository-controlled text read on every
importFrom lookup of a name the package does not export. Trim with two scans.
A backticked name is an identifier whose text keeps its backticks, so the
rename provider never matched `helper`(x) with helper. The call was left
behind, and a local `helper` <- function(...) was not seen as a shadow, so
a call to the local function was silently renamed. Match occurrences,
claims and bindings on the symbol, and compare the symbol in the safety
checks.
export(`%+%`) names the symbol %+%, while the definition node keeps its
spelling `%+%`, so the export check never matched and the function was
flagged as not exported (the same for exportPattern()). Compare the symbol
through one shared helper, rBareName.
The slot pattern matched only list(...) and representation(...), although
the field extractor accepts any call, so setClass("A", slots = c(a =
"numeric")) and setRefClass("R", fields = c(...)) created no Property nodes.
Accept c(...) for both. An unnamed string in slots = c("a", "b") or
fields = c("a", "b") names an untyped member, as in R; an unnamed string in
representation(...) or list(...) is still not a member. The new pattern
captures its callee as @_vecOwner, so the class-naming capture filter does
not mistake these strings for the class name.
attachDeferredROwners resolved the owner hint through the class registry,
which every language shares. A Python class Circle next to an R Circle made
the name ambiguous, so the R methods and slots lost their HAS_METHOD and
HAS_PROPERTY edges; and with no R Circle, an R setMethod attached to the
Python class.
Keep only candidates whose graph node is an R class before the uniqueness
test. The change is inside R's postParse hook, so shared code is untouched.
extractRParameters tested for identifier and argument nodes, but the grammar
emits one parameter node per formal, so it always returned an empty list.
Every R6 and RefClass method therefore had parameterCount 0 and a #0 id, and
a variadic method was not marked variadic.
Read the parameter nodes: the name field gives the formal, a default makes it
optional, and a dots name makes it variadic (such a method has no count and
no arity tag in its id).
Member ids carried no owner, so a second class in one file that shared a
member name with an earlier one (push, items, a setMethod generic, a slot)
minted the same id as the first and lost the member, along with its
HAS_METHOD or HAS_PROPERTY edge.
Register resolveDetachedOwnerName for R. It names the class declared by an
ancestor R6Class assignment, setClass or setRefClass call, or the signature
class of a setMethod call, so ids become Method:<file>:Stack.push#n and
Property:<file>:Stack.items. The owner edge is still attached by postParse
through ownerNameHint. The change is R-only: the hook is optional and
undefined for every other language.
The r-packages characterisation counts now include Child.greet, the member
the id collision used to drop.
r-provider.md: state that an empty or missing if arm is the header's seq
edge (control dependence labels it as the complement of the other arm);
extend the bare-callee limit to library(), require() and source(); say that
NAMESPACE raw strings and strings spanning lines are dropped without a
warning, which the logged-warning sentence did not cover; name the language
registrations that do mention R; give the devtools::load_all() rationale for
the unfiltered library() wildcard.
type-resolution-system.md: R binds no declaration, call-result or
method-call-result types (extractDeclaration is empty and there is no
extractPendingAssignment), and its cross-file propagation is partial, with a
test cited. Drop R from the footnote about synthesizeWildcardImportBindings(),
which does not exist.
Three comments named rProvider.expandsWildcardTo, which does not exist for R:
the pipeline consumes the hook from the ScopeResolver, so point them at
rScopeResolver.expandsWildcardTo. State the \x, \u and \U digit limits in
unescapeRString's comment, which are those of R's own parser.
Comments only.
The @param name pattern [\w.]*\w[\w.]* backtracks quadratically on a long
name followed by anything but a blank (32,000 characters took about half a
second per line), and parseRQualifier's lazy [\s\S]*? does the same on a
long run of blanks. Match [\w.]+ and require a word character separately,
and take the text before the first '::' directly. Accepted inputs and
results are unchanged (checked against the old patterns on two million
generated strings).
Tests cover large adversarial inputs and the dotted formal names.
inSet() scanned a class's ranges from the front on every state visit, but the
search loop charged one unit per visit regardless of class size, so a wide
class multiplied real work the budget never counted. Ranges are sorted,
merged and disjoint for every CharSet (normalise() for written classes,
constants and complement() for escapes), so a binary search is equivalent.
Charged work is unchanged.
Tests pin the reads per visit, the edges of a large class and the charged
work counters.
* fix(routes): reject middleware and route metadata false positives
* fix(routes): invalidate cached phantom routes on upgrade
* test(routes): cover cache upgrades across merged schema versions
Verify that schemas 137 through 140 invalidate both parse stores after merging main into PR #3561 and advancing the schema to 141.
---------
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
A reviewer's focused R run hit two worker-startup timeouts and had to rerun
with a 30-second readiness setting. The worker pool waits only 5 seconds by
default (GITNEXUS_WORKER_READY_TIMEOUT_MS), and every R test that parses
through the real worker pool started cold against that budget, unlike
csharp-razor-view-components and structural-pair-coverage, which already
raise it.
Apply the same idiom to the R tests that spawn the pool: stub the variable to
60000 in beforeAll and restore it with vi.unstubAllEnvs() in afterAll. Covers
r-worker-owner-absence, cfg/r-cfg-join, r-post-parse-gating, and the
resolvers/r*.test.ts files. rename-provider-r goes through
rename-integration-cases.ts, which already passes a 15-second budget to its
child process, so it is unchanged.
Tests only; no production code, schema or parse-cache change.
CONTRIBUTING.md (Native tree-sitter npm packages) requires the lockfile to pin
the audited grammars' tree-sitter peer to exactly 0.25.1, as it already does for
c, cpp, java, php, ruby, rust and typescript. The R grammar entry still carried
upstream's ^0.21.0. Only the peer range changes; the upstream peerDependenciesMeta
(including its tree_sitter key typo) is untouched.
Checked: npm ci --dry-run --ignore-scripts passes in the repository root and in
gitnexus/, npm ls --depth=0 reports nothing invalid, and a fresh npm pack
(including prepack) installed into an isolated prefix passes
scripts/assert-native-parsers.mjs (20 grammars, R included) with the R
manifest's peer at 0.25.1.
Both R paragraphs now say that --pdg builds a control-flow graph for R and that
rename is conservative. The shared Control flow paragraph no longer implies
reaching definitions for R, which has control flow only.
AUDITED_PEERS in scripts/prepare-tree-sitter-bundle.cjs lists nine manifests
since @eagleoutice/tree-sitter-r was added (r, c, cpp, java, php, ruby, rust,
javascript, typescript); CONTRIBUTING still said eight.
parseRoxygenParams read the parameter name with `\w+`, which stops at the first
dot, so `#' @param na.rm Data` documented nothing and the formal got no type
(the old unit test pinned that as "does not bind a dotted parameter name").
Reproduction: four new cases in type-env-r.test.ts (na.rm, .data,
max_n.rows, braced form with a continuation line) returned undefined instead of
'Data' on the unfixed pattern. The name pattern is now `[\w.]*\w[\w.]*`, so a
dotted identifier matches while `...` and `x,y` stay unbound; the former
"does not bind" case is replaced by positive cases plus a not-a-formal control.
expandRWildcardNames walked the flat localDefs of the target file and filtered
only dotted names, so a function nested inside another function
(`outer <- function() { helper <- function() 1 }`) was offered as a
package-level name to every file that calls library()/require() on the package.
Reproduction: two new cases in r-dotted-s3-hooks.test.ts failed on the
unfixed code (['outer', 'helper', 'top'] instead of ['outer', 'top'];
['a', 'b', 'c', 'd'] instead of ['a']). The expansion now keeps only names bound
at module scope (rFileTopLevel().names, which already feeds the dotted-tail
guard); top-level functions and bare-call S4/R6 classes are still listed
(controls added; test/integration/resolvers/r.test.ts unchanged and green).
The R rename guard compared the raw text of each `string_content` node with the
old and new name. An escaped spelling (`"f\u006fo"`, `'f\x6fo'`, octal) keeps
its escape inside string_content, and a raw string (`r"(foo)"`) has no
string_content node at all, so renaming `foo` proceeded while a get() or
do.call() on the escaped or raw spelling still resolved the old name.
Reproduction: 10 cases in the new test/unit/r-rename-string-guard.test.ts
returned 'ok' instead of unsupported_occurrence on the unfixed provider, and the
two new fixtures string-lookup-escaped and string-lookup-raw (rename-provider-r)
renamed successfully on the unfixed build. The guard now decodes each `string`
node (escapes, octal, raw strings) and compares the value.
docs/languages/r-provider.md said "spelled as a string literal"; it now states
the whole-value rule and what is still not detected (substring, paste0(),
non-indexed files).
skipConditionalBody returned just after the `if` of `else if (cond) body`, so
the main scan then read `(cond)` as stray text and emitted the body's
importFrom, S3method, export, exportClasses, exportMethods and exportPattern
directives as unconditional (the same for `if (a) if (b) body`).
Reproduction: 12 new cases in r-namespace-parser.test.ts failed on the
unfixed scanner (e.g. `if (a) {} else if (b) importFrom(x, y)` returned x::y;
`else if (b) export(hidden)` exported `hidden`). The nested `if (cond)` is now
stepped over iteratively (no recursion, tested with 50,000 nested ifs) and the
body that follows it is skipped.
LinearRegex.seed() filtered the closure's character states against the start
character before charging only 1 + (accepting states), so the range scans of
the failed state tests were free. A pattern of a few large character classes
could be probed with distinct start characters (each a filter miss, memoised
only up to 4096 keys) at almost no recorded cost.
Reproduction: for 20 alternated classes of 200 ranges, one miss on a
non-matching name recorded work 1 (expected >= 4000 range checks); a loop of
600 distinct start characters never exhausted a 1,000,000 budget. The filter
now charges 1 + ranges.length per examined state on a cache miss; hits stay
cheap. New tests in r-export-pattern-shared-budget.test.ts.