Skip to content

Insert full method call#49916

Merged
sharwell merged 30 commits intodotnet:features/argument-completionfrom
sharwell:insert-full-call
Feb 26, 2021
Merged

Insert full method call#49916
sharwell merged 30 commits intodotnet:features/argument-completionfrom
sharwell:insert-full-call

Conversation

@sharwell
Copy link
Contributor

@sharwell sharwell commented Dec 11, 2020

Implements the case from #12363 where a method takes one or more arguments.

  • This is an experimental feature which can be manually enabled by an option.
  • The new experience is triggered by Tab, Tab for invocations.
  • The initial implementation always infers simple default argument values. New ArgumentProvider heuristics will be added in follow-up.

Builds on #49909

image

image

@sharwell sharwell requested a review from a team as a code owner December 11, 2020 00:08
@sharwell sharwell marked this pull request as draft December 11, 2020 00:09
@sharwell sharwell force-pushed the insert-full-call branch 3 times, most recently from be84409 to aecdcd6 Compare December 14, 2020 23:39
Copy link
Contributor

@jmarolf jmarolf left a comment

Choose a reason for hiding this comment

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

:shipit:

Copy link
Contributor

@jmarolf jmarolf left a comment

Choose a reason for hiding this comment

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

After staring at this for a long time I can't think of a better way to do this with the APIs we have.

Copy link
Contributor

@CyrusNajmabadi CyrusNajmabadi left a comment

Choose a reason for hiding this comment

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

overall, i have no complaints. just some possible suggestions on targetted cleanup that might make things a bit more understandable. def curious if Jason's 'embed parameter info directly in snippet' approach could work.

@JoeRobich

This comment has been minimized.

@JoeRobich JoeRobich changed the base branch from release/dev16.10 to master January 27, 2021 00:13
Copy link
Member

@jasonmalinowski jasonmalinowski left a comment

Choose a reason for hiding this comment

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

Just more questions on how this thing is working...the statement management is still a bit murky to me.

Copy link
Member

Choose a reason for hiding this comment

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

Why the switch to SemanticModel?

Copy link
Contributor Author

@sharwell sharwell Jan 29, 2021

Choose a reason for hiding this comment

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

➡️ AnalyzerRunner was going to need it anyway. Document wasn't providing much. It was mentioned in earlier review comments, and I knew the change was eventually needed, so I implemented it.

Comment on lines +539 to +540
/// <param name="methodName">The name of the method as it should appear in code.</param>
/// <param name="includeMethod">
Copy link
Member

Choose a reason for hiding this comment

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

I guess can the docs say if includeMethod is false, then methodName should be ignored? I guess I'm not sure if "can be document separately" is really helping the clarity here.

// Create a snippet field for the argument. The name of the field matches the parameter name, and the
// default value for the field is provided by a call to the internal ArgumentValue snippet function. The
// parameter to the snippet function is a serialized SymbolKey which can be mapped back to the
// IParameterSymbol.
Copy link
Member

Choose a reason for hiding this comment

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

We don't know the signature to use until Signature Help tells us.

Exactly -- this method isn't running until you know the signature, but by this you can just directly put the values in.

but it increases coupling and would be even more difficult to understand.

I'd say this is far more coupled and more difficult to understand, since there's an implicit handshake between this code and the function implementation.

Copy link
Member

Choose a reason for hiding this comment

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

Any reason this can't just be Clear()? Yeah, it'll clear the value you're overwriting in the next line, but would mean it's more impervious to any other refactoring where somebody puts some other non-symbol state in that matters.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

➡️ The expansion session is still active at this point. Clear would not accurately reflect the state.

Copy link
Member

Choose a reason for hiding this comment

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

Which session? It's not starting until the next line, right? If there was a prior session it seems like we aren't caring about it at this point?

It's possible I'm just missing the scenario here -- I would have presumed this method is only called on the snippet picker UI and so at that point any session going away is moot, and there's nothing to preserve because it's not going to be a method expansion snippet being invoked.

Copy link
Contributor Author

@sharwell sharwell Feb 4, 2021

Choose a reason for hiding this comment

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

The presence of an active snippet session does not prevent the snippet picker from working, so while it's not common for this case to be hit with an active non-null session it is possible. Even if it's valid to clear the active session while it remains active, it would require substantially more explanation as to why doing so is OK.

In other words, among the available options for clearing state here, none of them is obviously correct and this one is the least confusing.

Copy link
Member

Choose a reason for hiding this comment

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

We feel confident there's no S_FALSE returns? 😄

Copy link
Contributor Author

@sharwell sharwell Jan 29, 2021

Choose a reason for hiding this comment

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

➡️ The documentation for IVsExpansionSession.GetFieldValue does not allow for S_FALSE returns, and the current code worked in practice.

Comment on lines 770 to 804
Copy link
Member

Choose a reason for hiding this comment

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

It feels like this really needs a comment here somewhere to explain the intent of this. It's...not obvious.

Copy link
Member

Choose a reason for hiding this comment

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

Also making a constant out of "placeholder" ("PlaceholderForEmptyArguments"?) would make this a bit less magic.

Copy link
Contributor Author

@sharwell sharwell Jan 29, 2021

Choose a reason for hiding this comment

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

Comment on lines +671 to +672
// It's unclear if/how this state would occur, but if it does we would throw an exception trying to
// use it. Just return immediately.
Copy link
Member

Choose a reason for hiding this comment

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

Want to at least change the comment since "It's unclear if/how this state would occur" isn't the case?

Copy link
Member

Choose a reason for hiding this comment

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

So I don't really see how _methods is being used here -- I see it being used in IsFullMethodCallSnippet but it seems like can just be a boolean state of whether we're in this mode or not?

Copy link
Contributor Author

@sharwell sharwell Jan 29, 2021

Choose a reason for hiding this comment

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

➡️ It probably could be changed, but this is going to have to change anyway as part of nested call support. Leaving it for now since changes in this space have caused undesired "iteration" churn (subtle fallout) that I don't want to go through again knowing the current form works.

Copy link
Member

Choose a reason for hiding this comment

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

Oh I think I see the bit I might be missing: if you do tab + tab on a method name we'll stick in parenthesis, we stick the methods here to recognize the name later. And this line:

var symbolName = _state._method?.Name ?? _state._methods.FirstOrDefault()?.Name;

Is just doing FirstOrDefault() because all the methods in the group have the same name? Yeah, this is really confusing. 😦

@dotnet dotnet deleted a comment from azure-pipelines bot Feb 3, 2021
Comment on lines +495 to +497
// This expansion is not derived from a symbol, so make sure the state isn't tracking any symbol
// information
_state.ClearSymbolInformation();
Copy link
Member

Choose a reason for hiding this comment

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

Is this just a paranoia check that we don't imagine this could happen, or can this happen in some way?

Copy link
Member

Choose a reason for hiding this comment

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

When is that coming?


if (expansion.InsertSpecificExpansion(doc, adjustedTextSpan, this, LanguageServiceGuid, pszRelativePath: null, out _state._expansionSession) == VSConstants.S_OK)
{
_state._preserveSymbols = false;
Copy link
Member

Choose a reason for hiding this comment

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

So it's not clear to me why _preserveSymbols is necessary here -- you're already holding onto the local to assert the symbols aren't changing, why not just instead just reassign the _state? Then you can get rid of _preserveSymbols?

Copy link
Contributor Author

@sharwell sharwell Feb 4, 2021

Choose a reason for hiding this comment

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

The API is reentrant (other methods in this class run during a deeply-nested call stack) and eventually end up back here. _preserveSymbols keeps those nested calls from changing state they aren't supposed to touch.

Copy link
Member

Choose a reason for hiding this comment

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

So as best I can tell from the review is the reentrancy concern here is when you call insert specific the prior session will be dismissed, which if we don't set that wipes out state. But what other call backs are happening that need the state? Is it just the snippet functions which we think can be removed?

Copy link
Member

Choose a reason for hiding this comment

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

Which session? It's not starting until the next line, right? If there was a prior session it seems like we aren't caring about it at this point?

It's possible I'm just missing the scenario here -- I would have presumed this method is only called on the snippet picker UI and so at that point any session going away is moot, and there's nothing to preserve because it's not going to be a method expansion snippet being invoked.

Comment on lines +1112 to +1116
/// <summary>
/// The current symbol presented in an Argument Provider snippet session. This may be null if Signature Help
/// has not yet provided a symbol to show.
/// </summary>
public IMethodSymbol _method;
Copy link
Member

Choose a reason for hiding this comment

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

Tracking item?

Copy link
Member

Choose a reason for hiding this comment

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

Oh I think I see the bit I might be missing: if you do tab + tab on a method name we'll stick in parenthesis, we stick the methods here to recognize the name later. And this line:

var symbolName = _state._method?.Name ?? _state._methods.FirstOrDefault()?.Name;

Is just doing FirstOrDefault() because all the methods in the group have the same name? Yeah, this is really confusing. 😦

@sharwell sharwell changed the base branch from master to features/argument-completion February 11, 2021 18:37
This issue was introduced by dotnet#50552 and caused signature help to
misbehave while typing.
Copy link
Member

@jasonmalinowski jasonmalinowski left a comment

Choose a reason for hiding this comment

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

Looks good to put in the feature branch; I think next steps are well-tracked.

@CyrusNajmabadi CyrusNajmabadi self-requested a review February 25, 2021 19:02
@CyrusNajmabadi CyrusNajmabadi dismissed their stale review February 25, 2021 19:46

alksdjla;sdjsl;ak dj

@sharwell sharwell merged commit 7736c18 into dotnet:features/argument-completion Feb 26, 2021
@sharwell sharwell deleted the insert-full-call branch February 26, 2021 01:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants