Repository navigation
feat(sess): resolve hover and completion targets without evaluating them - #1949
Conversation
Extract the member listing after `$` and `@` from handle_complete so that a completion handler which resolves its target differently can return the same items. Behavior is unchanged.
Add a shared parser and resolver for hover and completion requests. The request text is parsed but never evaluated: only a single name, a single constant, or a chain of `$` and `@` accesses on a name is accepted. The name is looked up along the search path and each accessor is applied with the base operator, so other clients cannot use these handlers to run arbitrary code and moving the cursor never runs an expression. Promises, active bindings and S3 `$` methods still run as before, and each binding on the path is read once. Missing names and members return NULL, while invalid requests, errors and interrupts are signalled. The existing socket handlers are not switched over yet.
Explain first that the editor's extraction can send code such as `repeat x`, and mention exposing the handlers to other clients only as a later use.
… members Completion has no items unless the trigger is `$` or `@`, so return before reading any binding instead of running promises or active bindings for nothing. Reject quoted members that cannot be symbols, such as `x$""`, as invalid requests. Collect the accessor chain without repeated prepending. The test now restores `T`, `$`, `@` and the S3 methods it masks if they existed before, so it can run in a live session.
The inspection tests for R6 active fields and S7 properties are skipped when the packages are missing. Listing them in Suggests makes CI install them; the managed sess installation only installs Imports.
renkun-ken
left a comment
There was a problem hiding this comment.
Reviewed aa188ec. The parser and fixed-accessor approach look sound within the stated scope, including retaining session-defined methods and deferring the socket-handler switch. I found one accessor compatibility issue below: the S4 slot precheck rejects a valid base @ pseudo-slot.
Validation: installed the exact PR source into an isolated library and ran the full sess tinytest suite on R 4.6.1: all 1,440 assertions passed, including the 99 new inspection assertions with R6 and S7 installed. All 10 GitHub checks are successful. No other actionable findings.
| return(list(found = FALSE)) | ||
| } | ||
| if (operator == "@") { | ||
| if (isS4(object) && !methods::.hasSlot(object, name)) { |
There was a problem hiding this comment.
[P2] Preserve base @ access to S4 pseudo-slots
methods::.hasSlot() is false for .S3Class on an ordinary S4 object, even though base @ successfully reads it. This precheck therefore reports a valid accepted target as missing before the fixed accessor can run. For example, on R 4.6.1:
methods::setClass("InspectionS4", slots = c(items = "list"))
inspection_s4 <- methods::new("InspectionS4", items = list(a = 1L))
inspection_s4@.S3Class # "InspectionS4"
methods::.hasSlot(inspection_s4, ".S3Class") # FALSE
sess:::handle_hover("inspection_s4@.S3Class") # list(str = ' chr "InspectionS4"')
sess:::inspection_hover("inspection_s4@.S3Class") # NULLThe same discrepancy occurs for S4 classes extending numeric or list. Please preserve base @'s special-slot behavior when deciding that an S4 member is missing, and add a regression test for .S3Class. This keeps the new resolver compatible with the existing hover handler when it is adopted.
methods::.hasSlot() is FALSE for .S3Class, for .Data when it is not stored as an attribute, and for names of a list-based object, although base @ reads all of them, so hovering such a member reported nothing. Accept these names in the precheck. Accessor errors still propagate.
renkun-ken
left a comment
There was a problem hiding this comment.
Reviewed 196d847. The S4 pseudo-slot fix addresses my previous P2 finding: inspection_hover("obj@.S3Class") now matches the existing hover handler. The new precheck preserves base @ behavior for .S3Class, .Data, and list-based names, while missing slots still return NULL and other errors propagate. The added regression tests cover the fix. No remaining actionable findings.
Validation: installed the exact updated source into an isolated library and ran the full sess tinytest suite on R 4.6.1: all 1,444 assertions passed, including 103 inspection assertions with R6 and S7 installed. Additional comparisons against base @ passed across S4 superclass types, and repeat inspection-test runs left no test bindings or classes behind. All 10 GitHub checks are successful.
Summary
Hover and completion requests send the text in front of the cursor to R, which runs
eval(parse(text = expr))in the global environment. The text is extracted in TypeScript with[a-zA-Z0-9._$@ ], and R trusts that extraction. Because spaces are included, valid R code other than a name can still reacheval(): hoveringcounterinrepeat counter <- counter + 1sendsrepeat counter, which parses and loops forever, so the R session hangs. Completion afterrepeat x$does the same.This PR adds a shared parser and resolver in sess that answers the same requests without evaluating the request text, so R no longer depends on what the client extracted. The existing socket handlers are not switched over yet, so behavior does not change in this PR; switching them is a follow-up.
Why requests must not be evaluated
$was typed, as therepeatexample shows. This concern was raised for vscode-R before in Improve$-completion of method-chaining syntax and more #1469, whose author concluded that a safe allowlist of functions could not be built. R's own completion (utils rcompgen) does not evaluate a prefix that contains an explicit function call, ark forbids function calls in completion evaluation, and IPython limits completion to side-effect-free evaluation by default.What is accepted
The request text is parsed in R and accepted only when it is a single expression that is one of:
mtcarsandpistill work);NULL, typedNA,Inf,NaN, strings). Evaluating a constant runs no code, and the editor already requests hover for them.TandFare names;$and@accesses on a name, with a name or non-empty string after each operator. Spaces such asa $ band trailing comments are fine.Anything else, including function calls,
[[,::, assignments, unary operators and multiple expressions, is rejected with an error of classsess_inspection_invalid. Previously only the first of several expressions was evaluated. Parsing in R, rather than splitting the text in TypeScript, keeps R's syntax (for example0x10,1L,1i) and its validation in one place.How accessors are applied
The parsed request is not evaluated. The resolver looks up the name and applies each accessor as a fixed call:
base::$orbase::@is embedded as a function object, the member is inserted as a symbol (the operators do not evaluate their member argument), and the call is evaluated in a private environment that binds the current object. As a result:$or@defined in the global environment do not change the operators;$methods defined there still dispatch as they do at the console;base::@is used instead ofmethods::slot()so that its own dispatch (S4 slots, S7 properties) is kept;Side effects that remain
Not evaluating the request does not make inspection free of side effects. Promises, active bindings (including R6 active fields), S3
$methods and methods forstr()or.DollarNames()still run code that is already defined in the session, as they do now. They are kept so that hover and completion keep working for lazy data, R6 objects and classes with custom accessors. Each binding on the path is read once.Results and errors
list(str = ...)from the existingcapture_str(). Completion returns the same items as before; the item builder is extracted fromhandle_complete()intocompletion_items()without changing it. Completion without a$or@trigger returnsNULLbefore reading any binding.@, but are not offered as completion items yet, becausecompletion_items()lists@members only for S4 objects. This is unchanged and noted with a TODO.$on a plain atomic vector, and@on a missing S4 slot or on an object without a class returnNULL.@accepts whatmethods::.hasSlot()reports plus the pseudo-slots that base@reads:.S3Class,.Dataand, on list-based objects,names. Like base@,.DatagivesNULLfor an object without a data part rather than an error.NULL, so that a caller with an execution deadline can tell them apart.Other changes
arity.tomlignoresdescription-malformed-version, which flags tidyverse-style development versions such as 3.4.9000.9000.