Repository navigation
[VS] Quick actions: Add USING, Generate method, Generate overrides, Convert to property - #2166
Merged
RobertvanderHulst merged 16 commits intoNov 9, 2026
Merged
Conversation
Pure helpers for the coming "Add USING" light bulb: which namespaces hold a type of the name under the caret (without the ones the file already uses or sits in), and where the USING line goes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The text of a method generated from a call: parameter names from the arguments (p1, p2 otherwise, duplicates numbered), known argument types or the dialect's untyped type (USUAL, OBJECT in Core), AS VOID in Core, a CLASS clause for VO-style classes without END CLASS. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When "Generate method" is offered and into which class: SELF calls and bare calls go into the enclosing class, oVar:Foo() into the variable's source class. Not for SUPER, chains, DLL/unknown receivers, names that exist in the class or a base class, classes with NoMethod, and bare calls to a known function or define. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The members a class can override (every method of a VO-dialect class, otherwise virtual/abstract/override ones; nearest base first, members already declared with the same kind, name and parameter types skipped) and the text of an override: same signature, REF/OUT kept, SUPER call, CLIPPER methods via _Args(), ACCESS/ASSIGN and properties. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One light-bulb source resolves the caret once per text version and position in the background and offers: - USING Namespace for a name that does not resolve, when a type of that name exists in another namespace (project, referenced projects and assemblies); - Generate method 'Foo' in class 'Bar' for SELF:Foo(), bare calls and calls on a variable typed as a source class; the method goes before END CLASS or, for VO-style classes, after the last member with a CLASS clause, also in another file (which is opened); - Generate overrides... with a dialog of the overridable base members. OverrideSkeleton gets the CLASS clause for VO-style classes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Generated VO methods end with RETURN NIL (no /vo9 needed); the caret goes on the empty body line. - Overrides of CLIPPER methods keep CLIPPER and the return type, also without parameters; abstract members throw NotImplementedException instead of calling SUPER; DLL properties get only their accessors; Finalize is not offered. - Already overridden members are recognised against DLL bases: parameter types compared by simple name, ACCESS/ASSIGN matched with properties. - Generate method is not offered for names the lookup resolves (extension methods) or for compiler pseudo functions (PCount, _Args...). - END CLASS is recognised by its keywords, so a VO-style class whose last line is END METHOD no longer gets members inserted inside it. - No overrides bulb when System.Object members are the only candidates. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- VS passes the caret line as range and calls the source off the UI thread: the caret is now taken from Caret.PositionChanged. - Base classes are resolved like Find References does (BaseType is only set by ForceComplete), with the USINGs of the class part in the file. - In VO an unknown name comes back as an undeclared variable: it counts as unresolved for "Add USING" and "Generate method". - "Add USING" only for names without a receiver (SELF:Missing offered USING System.Reflection) and also for types the lenient code model finds without their USING. - FindFunction & co. throw when nothing is found: treated as not found. - Argument types in X# names (INT instead of System.Int32); generated members take the indentation of the methods, not of the fields. - A log line per light-bulb query when logging is enabled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"Generate overrides..." is offered on the class header or on a blank line between members (followed by a member, a type, END CLASS or the end of the file), no longer everywhere in the class, and as a refactoring (screwdriver) instead of a fix. The enclosing class is taken from the member at the caret only when that member really holds the caret: on a class header the lookup returned the member before it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The ASSIGN parameter becomes VALUE on identifier tokens only (strings, comments and longer names stay), and RETURN x becomes RETURN; a longer expression (RETURN SELF:_c := c) is kept as a statement of its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The PROPERTY for an ACCESS/ASSIGN pair: one line (GET expr SET expr) when both bodies are a single expression on one line, else GET/SET blocks with the bodies re-indented; ACCESS only or ASSIGN only; index parameters; the wider visibility on the property, the narrower on its accessor; a final bare RETURN in the setter is dropped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pairs ACCESS and ASSIGN of a class by name and keeps only the pairs that convert without changing behaviour: not in classes without END CLASS, not for names a base class has, not when the other part is in another file, when types, index parameters or non-visibility modifiers differ, or when a member carries an attribute. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A refactoring on the header of an ACCESS or ASSIGN: "Convert to property" for the pair, "Convert all ACCESS/ASSIGN in class 'X' to properties" for every convertible pair of the class, in one undo step. The property takes the place of the ACCESS (else the ASSIGN); the comments above the removed member move with it; members in between stay. Headers with other modifiers than the visibility are left alone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Member names after : . -> :: are not renamed (SELF:oOwner := oOwner became SELF:VALUE := VALUE). - Preprocessor lines (#region, #endif ...) stay outside the moved and removed lines; a member with one inside is not converted. Only line comments move with a member, block comments are never cut. - A final RETURN after ";" no longer takes the statement before it. - RETURN expressions: without calls or assignments they are dropped, an assignment or call becomes a statement, anything mixed or a RETURN after ";" on its line is not converted. - The ASSIGN's index names are renamed to the ACCESS's. - Comments in a body force the GET/SET block form. - Not offered for headers with other modifiers, a CLASS clause or a ";" continuation, for INTERNAL vs PROTECTED, or for members outside the class block. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The generated method always has a return type now; it used to have none in
the VO dialect (RETURN NIL) and AS VOID without RETURN elsewhere.
- a call that is a statement of its own: AS VOID, body RETURN
- IF/ELSEIF/WHILE/CASE/UNTIL condition, operand of .AND./.OR./.NOT.: AS LOGIC
- LOCAL x := Foo() AS T: AS T
- x := Foo() / SELF:x := Foo(): the declared type of x
- RETURN Foo(): the return type of the enclosing member
- anything else: USUAL, or OBJECT in Core
A method with a result throws System.NotImplementedException{} until it is
written, as C# does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The light bulb only looked at the name under the caret, so "Generate method"
was offered on Foo in Foo(1, 2) but not with the caret inside the argument
list or after the closing parenthesis. When the caret is on no name, the
innermost call whose argument list holds the caret, or the call that ends the
statement before the caret, is used now. A literal array or a creation {...}
around the caret stops the search.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Summary
Quick actions (light bulb) for X# in the VS editor, the way C# offers them:
SELF:Foo(), bare calls in a class method andoVar:Foo()on a variable typed as a source class, also when the class is in another file (it is opened). It is offered with the caret on the name, inside the argument list or after the closing parenthesis.The return type comes from the call site, like C#:
o:Foo()as a statementAS VOID, bodyRETURNIF o:Foo(),WHILE,CASE,UNTIL,.AND./.OR./.NOT.AS LOGICLOCAL n := o:Foo() AS INTAS INTnCount := o:Foo(),SELF:nCount := ...nCountRETURN o:Foo()USUAL(OBJECTin Core)A method with a result throws
System.NotImplementedException{}until it is written. Parameter names come from the arguments, types from typed arguments.Not offered for names that exist in the class or a base class, classes with NoMethod, SUPER calls, chains, DLL or late-bound receivers, known functions/defines and compiler pseudo functions (PCount, _Args, ...).
GET expr SET exprwhen both bodies are a single expression, else GET/SET blocks. The ASSIGN parameter becomes VALUE; the wider visibility goes on the property, the narrower on its accessor.Only new files plus their entries in both LanguageService project files; no existing code changes.
Evidence
LanguageService.Tests/QuickActions/: Using, GenerateMethodRule, MethodSkeleton, CallResult, Override, OverrideTrigger, PropertyRule, PropertyConverter, AccessorBody, PropertyEdit and ConvertToProperty.Merge Danger
Door: two-way
The actions only change code when the user picks one, and each is one undo step.
Blast Radius: language service
A new light-bulb source; it runs the lookup in the background on caret moves (paused code-model walker, result cached per text version and caret position). An error there would show as a missing or wrong light bulb, not in other features.
The branch was tested against both orders with #2164 (inlay hints): it merges with that branch and with current dev without conflicts.
Known limitations
Bar(o:Foo())) gets USUAL/OBJECT, not the parameter type ofBar; that needs the overload resolution of [VS] Inlay hints: parameter names and VAR types #2164.o:Foo(nValue)), the actions are fornValue, not forFoo.🤖 Generated with Claude Code