Skip to content

Keep the initialization order in the MA0004 code fix - #1496

Merged
meziantou merged 1 commit into
mainfrom
feature/ma0004-using-initialization-order-05086b
Sep 12, 2026
Merged

meziantou merged 1 commit into
mainfrom
feature/ma0004-using-initialization-order-05086b

Conversation

@meziantou

Copy link
Copy Markdown
Owner

Problem

The MA0004 code fix extracts the declaration of an await using statement so that ConfigureAwait can be appended to the variable:

await using (var a = expr) { }
// becomes
var a = expr;
await using (a.ConfigureAwait(false)) { }

TryInsertVariableStatementBeforeUsing walked up through the enclosing using (expr) statements to find a position where a statement could be inserted, then hoisted the declaration above all of them. That moves the initializer ahead of every crossed using expression:

using (Create("outer"))
await using (var resource = Create("inner")) { }

is fixed into

var resource = Create("inner");
using (Create("outer"))
await using (resource.ConfigureAwait(false)) { }

Create("inner") now runs before Create("outer"). It also breaks exception safety: if the hoisted initializer throws, the resources acquired by the crossed using statements are never disposed, whereas originally they were.

Change

The helper no longer ascends. It inserts the declaration only when it can go directly before the using statement itself (its parent is a block, a switch section, or the global statement list). When the using statement is the embedded statement of another statement, the fixer falls back to wrapping the declaration and the using statement in a block, which it already did for the other positions where a statement cannot be inserted:

using (Create("outer"))
{
    var resource = Create("inner");
    await using (resource.ConfigureAwait(false))
    {
    }
}

The generated block now carries Formatter.Annotation, so its body is reindented instead of keeping the indentation it had. That fallback path had no test coverage before, so the formatting had gone unnoticed.

Tests

  • New regression test AwaitUsing_UnderUsingWithSideEffect_KeepsEvaluationOrder, with a side-effecting expression in the enclosing using and in the initializer.
  • AwaitUsing_WithMultipleUsings_ShouldNotThrow expected the hoisted form, which is exactly the transformation that reorders the acquisition, so it now expects the block form.

MA0004 tests pass on all five Roslyn versions (35 each), and the full Roslyn 5.9 suite passes (4576 tests, 0 failed, 0 skipped). dotnet run --project src/DocumentationGenerator produces no markdown change.

When extracting the declaration of an `await using` statement, the fixer
walked up through the enclosing `using (expr)` statements to find a place
where a statement could be inserted, then moved the declaration above all
of them. This evaluated the initializer before the expressions of those
`using` statements, and left their resources undisposed when the
initializer throws.

Insert the declaration only when it can go directly before the `using`
statement, and otherwise fall back to wrapping the declaration and the
`using` statement in a block, which the fixer already did for the other
positions where a statement cannot be inserted. That fallback now formats
the block it creates, as its body kept the indentation it had.
@meziantou
meziantou merged commit 3ccc05e into main Sep 12, 2026
13 checks passed
@meziantou
meziantou deleted the feature/ma0004-using-initialization-order-05086b branch September 12, 2026 04:33
meziantou added a commit that referenced this pull request Sep 12, 2026
Resolve the conflict in UseConfigureAwaitFixer with #1496, which removed the
walk through the enclosing using statements so the declaration is only
inserted directly before the using statement, and already added the
formatter annotation to the block fallback.

Keep that structure and check the variable name against the enclosing
function before inserting, so the scope is still preserved by the block
fallback when the name is used elsewhere.
This was referenced Sep 12, 2026
This was referenced Sep 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant