Skip to content

Detect get/set combination for GetStructuredToolTipText. - #15586

Merged
psfinaki merged 2 commits into
dotnet:mainfrom
nojaf:fix-tooltip
Jul 17, 2023
Merged

Detect get/set combination for GetStructuredToolTipText.#15586
psfinaki merged 2 commits into
dotnet:mainfrom
nojaf:fix-tooltip

Conversation

@nojaf

Copy link
Copy Markdown
Contributor

Fixes#15584.
Due to #15213, there are now two symbols for auto properties (which I still think is correct).
This leads to the double tooltip, as a first fix I'm only showing the tooltip for the getter.
Which was the original behaviour.
Ideally, this would show with get, set but that proves to be a bit more challenging.

@nojaf
nojaf requested a review from a team as a code ownerJuly 12, 2023 08:59
@auduchinok

auduchinok commented Jul 12, 2023

Copy link
Copy Markdown
Member

Due to #15213, there are now two symbols for auto properties (which I still think is correct).

I think it's not. There should be a single symbol for the property on the identifier range. The property symbol should then return the separate accessors symbols in GetterMethod and SetterMethod properties when applicable. It may be a bit tricky, since it may require some rewriting of the symbol logic for properties.

In addition to the property symbol, there should/may be accessor symbols reported at their declaration ranges.

We have another regression in tests showing exactly this case, where the additional setter symbol breaks things for us, and I was going to report it today as well. 🙂

@nojaf

Copy link
Copy Markdown
ContributorAuthor

Yeah, a single symbol that captures both get and set would indeed be the cleanest.
In my defence, my previous PR only highlighted an existing problem:

  • Two symbols were always created.
  • The setter was always ignored due to having the same name as the getter.

I did some investigation and it gets complicated after:

// Convert auto properties to let bindings in the pre-list
let recpreAutoProps memb =
match memb with
| SynMemberDefn.AutoProperty(ident = id)when id.idText =""->[]
| SynMemberDefn.AutoProperty(attributes=Attributes attribs; isStatic=isStatic; ident=id; typeOpt=tyOpt; propKind=propKind; xmlDoc=xmlDoc; synExpr=synExpr; range=mWholeAutoProp)->
// Only the keep the field-targeted attributes
letattribs= attribs |> List.filter (fun a ->match a.Target with Some t when t.idText ="field"->true|_->false)
letmLetPortion= synExpr.Range
letfldId= ident (CompilerGeneratedName id.idText, mLetPortion)
letheadPat= SynPat.LongIdent (SynLongIdent([fldId],[],[None]), None, Some noInferredTypars, SynArgPats.Pats [], None, mLetPortion)
letretInfo=match tyOpt with None -> None | Some ty -> Some (None, SynReturnInfo((ty, SynInfo.unnamedRetVal), ty.Range))
letisMutable=
match propKind with
| SynMemberKind.PropertySet
| SynMemberKind.PropertyGetSet ->true
|_->false
letattribs= mkAttributeList attribs mWholeAutoProp
letbinding= mkSynBinding (xmlDoc, headPat)(None,false, isMutable, mLetPortion, DebugPointAtBinding.NoneAtInvisible, retInfo, synExpr, synExpr.Range,[], attribs, None, SynBindingTrivia.Zero)
[(SynMemberDefn.LetBindings ([binding], isStatic,false, mWholeAutoProp))]
| SynMemberDefn.Interface (members=Some membs)-> membs |> List.collect preAutoProps
| SynMemberDefn.LetBindings _
| SynMemberDefn.ImplicitCtor _
| SynMemberDefn.Open _
| SynMemberDefn.ImplicitInherit _->[memb]
|_->[]
// Convert auto properties to member bindings in the post-list
let recpostAutoProps memb =
match memb with
| SynMemberDefn.AutoProperty(ident = id)when id.idText =""->[]
| SynMemberDefn.AutoProperty(attributes=Attributes attribs; isStatic=isStatic; ident=id; typeOpt=tyOpt; propKind=propKind; memberFlags=memberFlags; memberFlagsForSet=memberFlagsForSet; xmlDoc=xmlDoc; accessibility=access; trivia ={ GetSetKeywords = mGetSetOpt })->
letmMemberPortion= id.idRange
// Only the keep the non-field-targeted attributes
letattribs= attribs |> List.filter (fun a ->match a.Target with Some t when t.idText ="field"->false|_->true)
letfldId= ident (CompilerGeneratedName id.idText, mMemberPortion)
letheadPatIds=if isStatic then[id]else[ident ("__", mMemberPortion);id]
letheadPat= SynPat.LongIdent (SynLongIdent(headPatIds,[], List.replicate headPatIds.Length None), None, Some noInferredTypars, SynArgPats.Pats [], None, mMemberPortion)
letmemberFlags={ memberFlags with GetterOrSetterIsCompilerGenerated =true}
letmemberFlagsForSet={ memberFlagsForSet with GetterOrSetterIsCompilerGenerated =true}
match propKind, mGetSetOpt with
| SynMemberKind.PropertySet, Some gs -> errorR(Error(FSComp.SR.parsMutableOnAutoPropertyShouldBeGetSetNotJustSet(), gs.Range))
|_->()
[
match propKind with
| SynMemberKind.Member
| SynMemberKind.PropertyGet
| SynMemberKind.PropertyGetSet ->
letgetter=
letrhsExpr= SynExpr.Ident fldId
letretInfo=match tyOpt with None -> None | Some ty -> Some (None, SynReturnInfo((ty, SynInfo.unnamedRetVal), ty.Range))
letattribs= mkAttributeList attribs mMemberPortion
letbinding= mkSynBinding (xmlDoc, headPat)(access,false,false, mMemberPortion, DebugPointAtBinding.NoneAtInvisible, retInfo, rhsExpr, rhsExpr.Range,[], attribs, Some memberFlags, SynBindingTrivia.Zero)
SynMemberDefn.Member (binding, mMemberPortion)
yield getter
|_->()
match propKind with
| SynMemberKind.PropertySet
| SynMemberKind.PropertyGetSet ->
letsetter=
letvId= ident("v", mMemberPortion)
letheadPat= SynPat.LongIdent (SynLongIdent(headPatIds,[], List.replicate headPatIds.Length None), None, Some noInferredTypars, SynArgPats.Pats [mkSynPatVar None vId], None, mMemberPortion)
letrhsExpr= mkSynAssign (SynExpr.Ident fldId)(SynExpr.Ident vId)
letbinding= mkSynBinding (xmlDoc, headPat)(access,false,false, mMemberPortion, DebugPointAtBinding.NoneAtInvisible, None, rhsExpr, rhsExpr.Range,[],[], Some memberFlagsForSet, SynBindingTrivia.Zero)
SynMemberDefn.Member (binding, mMemberPortion)
yield setter
|_->()]
| SynMemberDefn.Interface (ty, mWith, Some membs, m)->
letmembs' = membs |> List.collect postAutoProps
[SynMemberDefn.Interface (ty, mWith, Some membs', m)]
| SynMemberDefn.LetBindings _
| SynMemberDefn.ImplicitCtor _
| SynMemberDefn.Open _
| SynMemberDefn.ImplicitInherit _->[]
|_->[memb]
letpreMembers= membersIncludingAutoProps |> List.collect preAutoProps
letpostMembers= membersIncludingAutoProps |> List.collect postAutoProps
preMembers @ postMembers

SynMemberDefn.AutoProperty gets transformed into three members:

  • SynMemberDefn.LetBindings to store the field
  • SynMemberDefn.Member for the getter
  • SynMemberDefn.Member for the setter

The members eventually end up in:

| SynMemberDefn.Member (bind, m),_->
// Phase2A: member binding - create prelim valspec (for recursive reference) and RecursiveBindingInfo
letNormalizedBinding(_,_,_,_,_,_,_,valSynData,_,_,_,_)as bind = BindingNormalization.NormalizeBinding ValOrMemberBinding cenv envForTycon bind
let(SynValData(memberFlagsOpt,_,_))= valSynData
match tcref.TypeOrMeasureKind with
| TyparKind.Type ->()
| TyparKind.Measure ->
match memberFlagsOpt with
| None ->()
| Some memberFlags ->
if memberFlags.IsInstance then error(Error(FSComp.SR.tcMeasureDeclarationsRequireStaticMembers(), m))
match memberFlags.MemberKind with
| SynMemberKind.Constructor -> error(Error(FSComp.SR.tcMeasureDeclarationsRequireStaticMembersNotConstructors(), m))
|_->()
letenvForMember=
match incrClassCtorLhsOpt with
| None -> AddDeclaredTypars CheckForDuplicateTypars copyOfTyconTypars envForTycon
| Some _-> envForTycon
letrbind= NormalizedRecBindingDefn(containerInfo, newslotsOK, declKind, bind)
letoverridesOK= declKind.CanOverrideOrImplement
let(binds,_values),(tpenv,recBindIdx)= AnalyzeAndMakeAndPublishRecursiveValue overridesOK false cenv envForMember (tpenv, recBindIdx) rbind
letcbinds=[for rbind in binds -> Phase2AMember rbind ]
letinnerState=(incrClassCtorLhsOpt, envForTycon, tpenv, recBindIdx, List.rev binds @ uncheckedBindsRev)
cbinds, innerState

And once AnalyzeAndMakeAndPublishRecursiveValue is called, they will be added to the Sinks in

match cenv.tcSink.CurrentSink with
| Some _whennot vspec.IsCompilerGenerated && shouldNotifySink vspec ->
letnenv= AddFakeNamedValRefToNameEnv vspec.DisplayName env.NameEnv (mkLocalValRef vspec)
CallEnvSink cenv.tcSink (vspec.Range, nenv, env.eAccessRights)
letitem= Item.Value(mkLocalValRef vspec)
CallNameResolutionSink cenv.tcSink (vspec.Range, nenv, item, emptyTyparInst, ItemOccurence.Binding, env.eAccessRights)
|_->()

Maybe

letshouldNotifySink(vspec:Val)=
match vspec.MemberInfo with
// `this` reference named `__`. It's either:
// * generated by compiler for auto properties or
// * provided by source code (i.e. `member _.Method = ...`)
// We don't notify sink about it to prevent generating `FSharpSymbol` for it and appearing in completion list.
| None when
vspec.IsBaseVal ||
vspec.IsMemberThisVal && vspec.LogicalName ="__"->false
|_->true

can be extended, not to report the two members.

And afterwards, report the AutoProperty somewhere around

@T-GroT-Gro left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK as a hotfix for the current tooltip behavior.

@auduchinok

Copy link
Copy Markdown
Member

@nojaf Thanks for such a good analysis!

Maybe ... can be extended, not to report the two members.

Or the getter and setter could be reported at the get and set ranges instead.

And afterwards, report the AutoProperty somewhere around

That sounds great, but I'm not sure yet how easy that would be. 🙂

@nojafnojaf mentioned this pull request Jul 12, 2023
@nojaf

Copy link
Copy Markdown
ContributorAuthor

Or the getter and setter could be reported at the get and set ranges instead.

Yeah, but when you have member val Foo = "bla" with get, set, if get and set correspond to the members, you do want to have some symbol for Foo right?

That sounds great, but I'm not sure yet how easy that would be. 🙂

Yeah, got somewhere in #15589 but it is a can of worms for sure.

@auduchinok

Copy link
Copy Markdown
Member

Yeah, but when you have member val Foo = "bla" with get, set, if get and set correspond to the members, you do want to have some symbol for Foo right?

Yes, something like FSharpMemberOrFunctionOrValue(cenv, P pinfo, Item.Property (pinfo.PropertyName, [pinfo])).

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

Tooltips: two tooltips are produced for auto property with accessors clause

4 participants

@nojaf@auduchinok@psfinaki@T-Gro