Repository navigation
[VS] Inlay hints: parameter names and VAR types - #2164
Merged
Merged
Conversation
The squash merge of X-Sharp#2156 on top of X-Sharp#2154 mixed the old body of FindReferencesCommand.SelectTarget with the Go To Implementation version (await in a non-async method, duplicate locals, undefined doc/name), so the language service did not compile. SelectTarget now uses TargetUnderCursor.Resolve, as Find References did in X-Sharp#2156, and keeps the TargetSelection result that Rename (X-Sharp#2154) needs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
XSharpLookup.nestedSearches is per thread now: lookups run in the background (inlay hints, Find References) next to QuickInfo on the UI thread and shared the recursion guard. ModelWalker.Suspend/Resume use Interlocked, they are called from several threads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HintCandidates finds calls with arguments (Name(...), Type{...}) and
implied declarations (VAR, LOCAL IMPLIED, FOREACH VAR, FOR VAR, USING VAR)
on lexer tokens. Declarations (FUNCTION Foo(...)) and OUT VAR are skipped.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HintRules decides which hints show: parameter names only for arguments
that say nothing (literals, NIL, NULL_*, signed numbers, Type{...}), only
when exactly one overload fits the call, none beyond the last parameter
or after the first PARAMS argument; extension methods called on an
instance skip their first parameter. Type hints are hidden when the
initializer names the type. Type names are shown without namespaces.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The double-click on a type hint writes the type: VAR x := e and LOCAL IMPLIED x := e become LOCAL x := e AS T (X# puts the type after the initializer), FOREACH VAR x IN c becomes FOREACH x AS T IN c. No edit for FOR, USING, STATIC and statements with several variables. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
InlayHintComputer resolves the candidates of a document snapshot on a private copy of its tokens (the classifier refills the live document), inside one ModelWalker.Suspend/Resume, and logs the time per file. A type hint is silent when the initializer ends in an ambiguous call or the type is unknown. InlayHintQueue runs one computation at a time for the IDE; a newer request for the same document replaces a queued one and cancels a running one; RequestDebounced waits for a quiet second. The classifier raises ParseCompleted after each parse. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
InlayHintTagger (IViewTaggerProvider, IntraTextAdornmentTag) shows the hints of the last computed snapshot, translated to the current one; a hint whose text was edited disappears until the next result. Hover shows the parameter (with its XML doc and the member prototype) or the type; a double-click on a type hint writes the type (one undo step). Colours in Fonts and Colors (X# Inlay Hint). Options: Show parameter name hints, Show inferred type hints (both on). Open documents are recomputed after a quiet second when the code model walker is done (new ModelWalker.WalkCompleted) or another file of the project was saved. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
VO METHOD Open(cName, lShared) accepts any number of arguments, but the code model only marks CLIPPER when the keyword is written. Inlay hints were missing for calls with fewer or more arguments (Find References kept them only by the no-match fallback). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fonts and Colors stores colours without alpha: the hint background came back as opaque #808080, the same grey as the text, and the hints showed as empty grey boxes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… text
The parser stores the initializer text as the type name of a VAR; when
the lookup cannot resolve it, that text stays. FOREACH VAR d IN
<DbFile>{o} showed ' AS <DbFile>{o}' and double-click wrote it out.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FOREACH VAR d IN <DbFile>{o} now types d as DbFile. VAR list := s:Load()
kept List<T> instead of the declared List<STRING>, so FOREACH VAR item
IN list could not type item; implied assignments now keep the type
arguments of the declaration, as FOREACH variables already did.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Type hint stays silent when the overloads of the final call return different types: the lookup takes the first overload, not the one the arguments pick (Math.Max(1, 2) said BYTE). - Type hint stays silent when a type argument does not resolve (List<TSource> from ToList(), List<T> from a generic class). - The double-click edit writes the namespace unless a USING or the enclosing namespace brings it in, so the edit compiles. - The lookup's array type cache is a ConcurrentDictionary: the lookup now also runs in the background. - The hint computation no longer suspends the code model walker; a suspend made the walker drop and restart its current project after every parse. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The tooltip of a hint was a plain WPF ToolTip with plain text: white in every theme and without syntax colours. It now uses the editor's IToolTipService with the same content elements as the X# QuickInfo (icon + classified text), so it follows the theme and the classification colours like the C# hints do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
As in C#: a third option "Show all hints while pressing Alt+F1" (on by default) shows the switched off kinds while Alt+F1 is held down in an X# editor. Releasing F1 or Alt, or moving the focus away, hides them again at once. A key processor tracks the hold; the tagger computes the missing kinds without the debounce and filters by the current setting. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
XProject.GetType rebuilds a source type from the database on every lookup. For each member, XDbResult.UpdateLocation looked the file up through XSolution.FindFile, which creates a new XFile and reads it from the database; GetType then replaced that file with its own cached one. For a VO class with many members this was most of the time of a lookup. UpdateLocation now takes the file from the caller: GetType passes the file it resolves per file id, the extension method path looks its single file up once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A large file was resolved as a whole before any hint appeared. The tagger now computes the visible lines and one screen above and below, and when the view scrolls to lines that are not covered it computes those after a short pause and merges them with the hints of the same snapshot. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HintCandidates.Calls looked each call name up in the token list to find its arguments. In the editor the token index does not match the position in that list, so every call searched the whole list: a 775 KB file spent about a second on this before any call was resolved. OverloadFilter.Arguments now also takes the position, and the scan passes it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The hints were grey on a translucent grey background in every theme. They now take the colours of the C# "inline hints" item in Fonts and Colors: the Visual Studio themes colour it, so the hints follow light and dark like the C# hints, and a colour the user picks for C# applies to both. Without that item (no Roslyn) the X# item is used, now with the C# defaults. It is no longer shown in Fonts and Colors, where it would change nothing while the C# item exists. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
VAR d := Dictionary<STRING, INT>{} was split at the comma between the type
arguments into two declarators. The double-click edit then wrote the type
after STRING, in the middle of the type arguments, and the code no longer
compiled. The commas of Name<A, B>{...}, Name<A, B>(...) and <T>{...} now stay
in the initializer; a comparison such as VAR a := x < y, b := 2 still splits.
A generic constructor (VAR l := List<STRING>{}) now counts as a visible type,
like Customer{}, so it no longer gets a redundant type hint.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The element type name of a typed array literal was stored as the type of the loop variable before the lookup searched it, so an unknown T showed as an inlay hint (and in QuickInfo). The name is now only taken when the type resolves; otherwise the variable stays unresolved and gets no hint. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cument A pending debounced computation was asked for an older state; after a direct request for the same document it ran once more for nothing. Request now cancels it. The CancellationTokenSources of the debounce are disposed when their delay ends. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…uments
OverloadFilter.Arguments split Foo(Dictionary<STRING, INT>{}, 1) at the comma
between the type arguments and counted three arguments. Find References then
dropped the matching overload, and the inlay hints found no parameter for the
call or shifted the names of a CLIPPER method. The commas of Name<A, B>{...},
Name<A, B>(...) and <T>{...} now stay in their argument; a comparison such as
Foo(a < b, c > d, e) still counts three.
The type argument check moved from HintCandidates to OverloadFilter, where
both use it; it now skips tokens off the default channel.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e name
List<STRING>{10} and o:Foo<INT>(1, 2) had no argument list: the argument
reader and the inlay hint candidates expected ( or { right after the name,
and a generic creation was not narrowed to the constructors. So these calls
got no parameter hints, and Find References did not filter their overloads.
OverloadFilter.ListAfterName finds the ( or { after the name, also after
type arguments; Arguments, SymbolResolver.ForCreation and
HintCandidates.Calls use it. Foo < 3 and Foo < b, c > d stay no call.
Go To Definition on Foo in Foo<T>{1} now reaches the constructor, as it
already did for Foo{1}.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CLASS Box<T> is stored as Box`1. Find References searched the files for
"Box`1", which no source contains, and did not recognise the declaration,
so a generic class had no references at all. The search name and the
declaration check now drop the arity.
The constructor candidates only took Box{...}; Box<INT>{...} is now one too
(OverloadFilter.ListAfterName), so Find References on the CONSTRUCTOR of a
generic class finds its creations.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
F12 on a keyword (the caret on CLASS) showed "Cannot navigate to the symbol
under the cursor"; when nothing is found F12 stays silent, a keyword is now
treated the same. Seen after F12 on Foo{1} through DTE.ExecuteCommand: the
shell sent the command twice, the first call opened the class with the caret
at the start of its line, the second one hit the CLASS keyword.
The deduplication moved into XSharpGotoDefinition.Targets, which also drops
the keywords.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
As C# (Roslyn: EnabledForParameters and EnabledForTypes false, DisplayAllHintsWhilePressingAltF1 true): parameter and type hints are off until switched on in Tools - Options; holding Alt+F1 shows both. The defaults are constants in InlayHintSettings, used by the options page and as the start values. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5df8da4 dropped the end check in HintRules.IsConstructor: for an argument or initializer made only of IDs and dots (Foo(Color.Red), VAR e := Encoding.UTF8) it read past the end of the token list. The exception left the computation, so the whole range showed no hints. Found in review. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merging with the older hints of the same snapshot kept every old hint the fresh result had no hint for at the same place, also inside the range just recomputed. After the walker finished or another file was saved, a call that had become ambiguous kept its stale parameter name until the next edit. Inside the freshly covered range only the fresh hints count now. Found in review. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…time limit in the linear-time test FinalCallName took the token before the argument list as the call name, so for VAR x := o:Foo<INT>(1) it found '>' and skipped the check that the call is unambiguous; the type hint could come from the wrong overload. It now steps back over the type arguments to the name (checked with OverloadFilter.ListAfterName). ALargeFileIsScannedInLinearTime had a 1000 ms wall-clock limit that can fail on the E-cores. It now compares 5000 with 20000 calls (fastest of three runs each): linear is about 4x, the test allows less than 8x; the quadratic scan it guards against measured 23x. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
f80b56f says: "Go To Definition on Foo in Foo<T>{1} now reaches the constructor, as it already did for Foo{1}." That is wrong. Correct is: Find References on Foo in Foo<T>{1} now resolves to the constructors, as it already did for Foo{1}; Go To Definition still goes to the class, for Foo{1} as well. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflict in FindReferencesCommand.cs: 7a0507a and X-Sharp#2161 are the same build fix; took the dev version. 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
Inlay hints for X# in the VS editor: parameter names at call sites and the inferred type of
VARdeclarations, the way C# shows its inline hints.Type{...}), and only when exactly one overload fits the call.VAR,FOREACH VARandFOR VARwhen the initializer does not already show the type and the lookup resolves it. Double-clicking a type hint writes the type into the code.Changes to shared code that other features use too:
Lookup: thread-safe state, and the walker pauses while hints are computed.
FOREACH VARnow works over a typed array literal, over aVARlocal and over<T>{}.OverloadFilter:
Foo<T>(...)andFoo<T>{...}are recognised as calls.Find References benefits from all three.
Find References now finds generic source classes (
Box`1) and their constructors.Go To Definition: a keyword is no longer a target. This removes the "Cannot navigate to the symbol under the cursor" message after F12 on a creation.
Code model (
XProject.prg,ModelWalker.prg): building a source type no longer looks up the file for each member.Evidence
Tests: LanguageService.Tests run 330/330. New test classes:
OverloadFilter, FindReferencesEngine and ReferenceCandidateScanner have new cases.
Performance:
ALargeFileIsScannedInLinearTimeguards against the quadratic scan; with it put back in, 4× the calls took 23× as long.Checked by hand in VS 2022:
The project builds for VS 2019 and VS 2022; only VS 2022 was checked by hand.
Merge Danger
Door: two-way
The hints are off by default, and nothing is stored in the user's code unless they double-click a hint.
Blast Radius: language service
The shared lookup, OverloadFilter,
SymbolResolver.ForCreationand the code model changed. A regression there would show in Find References, Go To Definition / Implementation or completion, not only in the hints.Known limitations (deferred)
Foo(a < b, c > (d))is read as type arguments (the same rule as C#).Box→Box`1resolution, and the tagger's Alt+F1 flow.🤖 Generated with Claude Code