Skip to content

Conversation

@genlu
Copy link
Member

@genlu genlu commented Mar 1, 2023

and check for empty change range before merge.

An attempt to fix the error reported by @jasonmalinowski

    <description>System.ArgumentException: newChanges&#x000D;&#x000A;
    at Roslyn.Utilities.TextChangeRangeExtensions.Merge(ImmutableArray`1 oldChanges, ImmutableArray`1 newChanges)&#x000D;&#x000A;
    at Microsoft.CodeAnalysis.CSharp.BraceCompletion.AbstractCurlyBraceOrBracketCompletionService.&lt;GetTextChangeAfterReturn&gt;g__GetMergedChanges|5_2(TextChange newLineEdit, ImmutableArray`1 formattingChanges, SourceText formattedText)&#x000D;&#x000A;
    at Microsoft.CodeAnalysis.CSharp.BraceCompletion.AbstractCurlyBraceOrBracketCompletionService.GetTextChangeAfterReturn(BraceCompletionContext context, IndentationOptions options, CancellationToken cancellationToken)&#x000D;&#x000A;
    at Microsoft.CodeAnalysis.AutomaticCompletion.BraceCompletionSessionProvider.BraceCompletionSession.PostReturn()&#x000D;&#x000A;
    at Microsoft.VisualStudio.Text.Utilities.GuardedOperations.CallExtensionPoint(Object errorSource, Action call)</description>

https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1758005

@genlu genlu requested a review from a team as a code owner March 1, 2023 04:04
@ghost ghost added the Area-IDE label Mar 1, 2023
@genlu
Copy link
Member Author

genlu commented Mar 2, 2023

@jasonmalinowski @CyrusNajmabadi Not sure how to trigger this exception in test. Any suggestion?

@jasonmalinowski
Copy link
Member

@genlu Off the top of my head no idea -- although I hit it again today. :-/ Do we have dumps from telemetry? Those might let you look at the edit history of the buffer and see if there was a certain lead up of edits.

@genlu
Copy link
Member Author

genlu commented Mar 2, 2023

@jasonmalinowski so far we don't have anything. Maybe we can take this fix with the IsEmpty check removed so we can take another look if we still have issue?

Looks like this is not a completely new issue. I saw same stacktrace from 17.2 during last week.

{
var formattingChangeRanges = formattingChanges.SelectAsArray(f => f.ToTextChangeRange());
if (formattingChangeRanges.IsEmpty)
return ImmutableArray.Create(newLineEdit);
Copy link
Member Author

Choose a reason for hiding this comment

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

@jasonmalinowski tbh, I'm not sure how could we make the assumption that calling FormatTrackingSpan with a newline inserted would always return a non-empty change. Wouldn't it depend on user settings?

Copy link
Member

Choose a reason for hiding this comment

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

@genlu I'm not sure I follow the question.

Copy link
Member Author

Choose a reason for hiding this comment

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

We add a newline between brackets in some cases, and then pass the changed document to format the span between brackets. The assumption is the format change will never be empty as long as we inserted a newline, which I don't think it's guaranteed.

Copy link
Member

Choose a reason for hiding this comment

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

yeah, i don't expect we would have that guarantee. tbh, i really don't get what this code (including prior to your change) is trying to do and how it is going about trying to do it. Do you feel confident we have good test coverage here to catch issues?

Copy link
Member Author

Choose a reason for hiding this comment

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

OK, after a few attempts on tweaking the options, I finally get a test to repro the crash in both 17.5 branch (which is before #66575) and main

[WpfFact]
public void Test()
{
    var code = @"namespace NS1
$$";
    var expected = @"namespace NS1
{}";
    var expectedAfterReturn = @"namespace NS1
{

}";
    var globalOptions = new OptionsCollection(LanguageNames.CSharp)
    {
        { FormattingOptions2.SmartIndent, FormattingOptions2.IndentStyle.None },
        { AutoFormattingOptionsStorage.FormatOnCloseBrace, false },
    };
    using var session = CreateSession(code, globalOptions);
    Assert.NotNull(session);
    CheckStart(session.Session);
    Assert.Equal(expected, session.Session.SubjectBuffer.CurrentSnapshot.GetText());
    CheckReturn(session.Session, 0, expectedAfterReturn);
}
[xUnit.net 00:00:05.25]     Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.AutomaticCompletion.AutomaticBraceCompletionTests.Test [FAIL]
[xUnit.net 00:00:05.25]       System.ArgumentException : newChanges
[xUnit.net 00:00:05.25]       Stack Trace:
[xUnit.net 00:00:05.25]         src\Compilers\Core\Portable\InternalUtilities\TextChangeRangeExtensions.cs(129,0): at Roslyn.Utilities.TextChangeRangeExtensions.Merge(ImmutableArray`1 oldChanges, ImmutableArray`1 newChanges)
[xUnit.net 00:00:05.25]         src\Features\CSharp\Portable\BraceCompletion\AbstractCurlyBraceOrBracketCompletionService.cs(176,0): at Microsoft.CodeAnalysis.CSharp.BraceCompletion.AbstractCurlyBraceOrBracketCompletionService.<GetTextChangeAfterReturn>g__GetMergedChanges|5_2(TextChange newLineEdit, ImmutableArray`1 formattingChanges, SourceText formattedText)
[xUnit.net 00:00:05.25]         src\Features\CSharp\Portable\BraceCompletion\AbstractCurlyBraceOrBracketCompletionService.cs(153,0): at Microsoft.CodeAnalysis.CSharp.BraceCompletion.AbstractCurlyBraceOrBracketCompletionService.GetTextChangeAfterReturn(BraceCompletionContext context, IndentationOptions options, CancellationToken cancellationToken)
[xUnit.net 00:00:05.25]         src\EditorFeatures\Core\AutomaticCompletion\BraceCompletionSessionProvider.BraceCompletionSession.cs(290,0): at Microsoft.CodeAnalysis.AutomaticCompletion.BraceCompletionSessionProvider.BraceCompletionSession.PostReturn()
[xUnit.net 00:00:05.25]         src\EditorFeatures\TestUtilities\AutomaticCompletion\AbstractAutomaticBraceCompletionTests.cs(86,0): at Microsoft.CodeAnalysis.Editor.UnitTests.AutomaticCompletion.AbstractAutomaticBraceCompletionTests.CheckReturn(IBraceCompletionSession session, Int32 indentation, String result)
[xUnit.net 00:00:05.25]         src\EditorFeatures\CSharpTest\AutomaticCompletion\AutomaticBraceCompletionTests.cs(1694,0): at Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.AutomaticCompletion.AutomaticBraceCompletionTests.Test()

@genlu genlu requested review from a team, 333fred and JoeRobich as code owners March 13, 2023 23:07
@genlu genlu requested a review from a team March 13, 2023 23:07
@genlu genlu requested a review from a team as a code owner March 13, 2023 23:07
var newLineString = options.FormattingOptions.NewLine;
newLineEdit = new TextChange(new TextSpan(closingToken.FullSpan.Start, 0), newLineString);
var closingToken = document.Root.FindToken(closingPoint - 1);
Debug.Assert(IsValidClosingBraceToken(closingToken));
Copy link
Member

Choose a reason for hiding this comment

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

i dont' love that thsi is only an assert. maybe bail out here since if we don't find the close token, everything is going to go bad?

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't think these are actually needed. If we can't find the token here then something outside this method must be seriously wrong. In that case I guess we'd like this to crash so we can get dump?

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 my question is: how do we know we'll have a close token here? I don't know this code well enough, so this is a question to you about what code ran prior to this that absolutely made us certain we have a good tree, and the tree will think there's a clsoe brace at this position :)

Copy link
Member Author

@genlu genlu Mar 14, 2023

Choose a reason for hiding this comment

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

Ah, I see. From what I saw, the code prior to this handles the insertion of closing brace here. Only then this method would be called when 'return' is typed in between the braces. In other words, the close brace is added as apart of session start.

@genlu
Copy link
Member Author

genlu commented Mar 14, 2023

@CyrusNajmabadi Any more comments?

@CyrusNajmabadi
Copy link
Member

looking.

CheckStart(session.Session);
Assert.Equal(expected, session.Session.SubjectBuffer.CurrentSnapshot.GetText());

CheckReturn(session.Session, 0, expectedAfterReturn);
Copy link
Member

Choose a reason for hiding this comment

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

can you clarify what is was about this prior code that was causing the problem?

Copy link
Member Author

Choose a reason for hiding this comment

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

We previous assume calling formatter on the brace pair (including added newline in-between) would always cause text changes. But it's not true with certain option combination, which would cause us the throw.

Copy link
Member

Choose a reason for hiding this comment

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

thanks!

document = document.WithChangedRoot(rootToFormat, cancellationToken);
var rootToFormat = document.Root.ReplaceToken(closingToken, newClosingToken);

newClosingToken = rootToFormat.FindToken(closingPoint - 1);
Copy link
Member

Choose a reason for hiding this comment

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

instead of this, use an annotation. that will be much safer and more correct. i really just do not trust thsi code.

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure. Fixed

closingPoint = newClosingToken.Span.End;

var textChangeLength = newClosingToken.Span.End - closingToken.Span.End;
newLineEdit = new TextChange(new TextSpan(closingToken.FullSpan.Start, 0), newClosingToken.ToFullString()[..textChangeLength]);
Copy link
Member

Choose a reason for hiding this comment

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

ok. trying to reason about this. we're saying that we go to the start of where the close token was. then we figure out the distance the close token moved. Then we grab that distance from the start of teh new closing token?

I'm not really getting it. Can we be explicit about what text we're trying to grab here? It looks like it's just supposed to be the potential newlines at the start of the close token? Can we just grab this from it's trivia instead? Or, even better, can we just grab from the source-text?

The reason i don't like this is that ..TextChangeLength looks like it could grab random stuff (including the token contents itself).

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed

// Depending on options, we might not get any formatting change.
// In this case, the newline edit is the only change.
if (formattingChanges.IsEmpty)
return ImmutableArray.Create(newLineEdit.Value);
Copy link
Member

Choose a reason for hiding this comment

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

consdier moving this into GetMergedChanges.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

@genlu genlu enabled auto-merge (squash) March 15, 2023 20:22
@genlu genlu merged commit e7fe653 into dotnet:main Mar 15, 2023
@ghost ghost added this to the Next milestone Mar 15, 2023
@genlu genlu deleted the Fix branch March 22, 2023 22:09
@allisonchou allisonchou modified the milestones: Next, 17.6 P3 Mar 28, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants