Show hover tooltips for dynamic member accesses#3906
Merged
Conversation
Dynamic member accesses and invocations carried only the member name (DynamicMemberResolveResult / DynamicInvocationResolveResult), so GetSymbol returned null and the editor emitted no reference or hover. Synthesize a member on the target type - a dynamic field for a member access, a dynamic-returning method for a member invocation - named after the accessed member and typed from the callsite delegate: each argument uses its recorded compile-time type when the binder set one (statically typed or constant arguments), dynamic otherwise, and the declaring type comes from the receiver's argument info. Route these through GetSymbol; TextTokenWriter and the hover renderer already turn an IEntity into a tooltip. The synthesized members have no metadata token, so they render a signature on hover but are not navigation targets. Assisted-by: Claude:claude-fable-5:Claude Code
A member synthesized for a dynamic access has no metadata token, so the navigation link it produced went nowhere. Emit it as a local-style reference (WriteLocalReference) instead: the hover renderer still shows the signature (it resolves any IEntity reference regardless of IsLocal), but the identifier is no longer a navigation target, matching how local variables are treated. The written text is unchanged. Assisted-by: Claude:claude-fable-5:Claude Code
Local variable references are emitted as reference segments (via WriteLocalReference), but their reference object is an ILVariable, which BuildHoverContent could not resolve to an IEntity - so hovering a local showed nothing. Render the declared type (syntax-highlighted, via a new Language.GetRichText(IType) overload) with the name and whether it is a parameter or a local, directly from the ILVariable. Assisted-by: Claude:claude-fable-5:Claude Code
The dynamic keyword fell through WritePrimitiveType's default arm, so it was emitted as plain text with no reference segment and thus could not carry a tooltip - unlike int/string/object, which reference their metadata type. Emit dynamic as a hover-only reference to SpecialType.Dynamic (no navigation target, since it has no metadata definition) and render it in BuildHoverContent. Assisted-by: Claude:claude-fable-5:Claude Code
A dynamic object creation (new T(b) where an argument is dynamic) gave its ObjectCreateExpression only a plain ResolveResult(T), so neither the type name nor the parentheses referenced a constructor - unlike a normal new expression, whose CSharpInvocationResolveResult lets both hover the ctor. Synthesize a constructor on the created type (parameters typed from the callsite delegate) and attach it the same way. Since it has no metadata, route it hover-only, like the other dynamic members, so the type name's existing navigation is not replaced by a dead link. Assisted-by: Claude:claude-fable-5:Claude Code
References had two behaviors encoded in one bool: navigable links, and "local" references that are non-navigable but highlight all occurrences on click. Synthesized dynamic members were routed through the latter, so they picked up the occurrence highlight even though each use is a distinct synthetic member with nothing to group. Model the three modes explicitly with a ReferenceMode enum on ReferenceSegment (Link / LocalHighlight / HoverOnly). Dynamic members, the dynamic constructor and the dynamic keyword now use HoverOnly: they still show a hover tooltip (BuildHoverContent resolves any IEntity reference regardless of mode) but get the arrow cursor, do not navigate, and do not highlight. Local variables keep LocalHighlight. Plumbed via a new isHoverOnly flag on ITextOutput.WriteLocalReference. Assisted-by: Claude:claude-fable-5:Claude Code
A dynamic invocation with explicit type arguments (a.Method<int>()) synthesized a non-generic fake method, so the hover showed Method(...) without the generic list. Give the fake a matching set of conventionally named type parameters (T, T2, ...) and specialize it with the actual arguments, as a real generic call produces a SpecializedMethod, so the hover shows the method's generic parameters. Assisted-by: Claude:claude-fable-5:Claude Code
Lock the hover content the dynamic-tooltip work produces. Ambience-level cases (CSharpAmbienceTests) pin that SpecialType.Dynamic renders as "dynamic" and that a synthetic dynamic method renders its return and per-argument types - including the full hover form, confirming the unnamed synthetic parameters collapse to their types with no dangling name. An end-to-end case (HoverOnlyReferenceTests) decompiles a dynamic call and renders the symbol GetSymbol hands back, exercising the actual synthesis (argument typing from the callsite delegate), not a hand-built stand-in. Assisted-by: Claude:claude-fable-5:Claude Code
A dynamic index access (a[b]) gave its IndexerExpression a DynamicInvocationResolveResult with no symbol, so the brackets carried no tooltip. Synthesize an indexer (FakeProperty, IsIndexer) on the target type with the index parameters typed from the callsite delegate, and attach it. Route it hover-only by detecting a DynamicInvocationResolveResult directly on the node - which also covers an invoke-member's own parentheses, so those stop producing a dead navigation link too. Assisted-by: Claude:claude-fable-5:Claude Code
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.
Dynamic member accesses (
d.Property,d.Method(...),d[index],new T(dynamicArg)) previously produced no tooltip at all in the text view, because there is no metadata entity behind them. This PR gives them proper hovers by synthesizing a symbol from the encoded callsite:DynamicMemberResolveResult/DynamicInvocationResolveResultgain aSymbol, populated inExpressionBuilderwith a synthesizedFakeField/FakeMethod/FakeProperty(member get/set, invoke-member incl. generic type parameters, get/set index, constructor). Parameter/return types come fromCSharpArgumentInfo.CompileTimeTypewhere the compiler recorded a static type, elsedynamic.ReferenceSegment.IsLocalbecomesReferenceMode { Link, LocalHighlight, HoverOnly }. Synthesized dynamic members areHoverOnly— tooltip, arrow cursor, but no navigation and no occurrence highlight, since there is no real member to jump to. Local variables keep the highlight behavior.dynamickeyword itself and local variables/parameters also get hovers.The decompiled text output is unchanged; only reference/tooltip metadata is affected. Covered by new
HoverOnlyReferenceTests(end-to-end through the headless UI),CSharpAmbienceTestsrendering cases, and the existing dynamic pretty tests stay green.🤖 Generated with Claude Code