Skip to content

[mixed object and collection initializers] Pass 3a/3 of IDE0017+IDE0028 unification: merged walk#83761

Draft
CyrusNajmabadi wants to merge 119 commits into
dotnet:features/compound-assignment-in-initializerfrom
CyrusNajmabadi:mixed-init-unify-pass3-walk-merge
Draft

[mixed object and collection initializers] Pass 3a/3 of IDE0017+IDE0028 unification: merged walk#83761
CyrusNajmabadi wants to merge 119 commits into
dotnet:features/compound-assignment-in-initializerfrom
CyrusNajmabadi:mixed-init-unify-pass3-walk-merge

Conversation

@CyrusNajmabadi
Copy link
Copy Markdown
Contributor

@CyrusNajmabadi CyrusNajmabadi commented May 18, 2026

Third pass of the IDE0017+IDE0028 unification — Pass 1 (#83758) unified the match data shape, Pass 2 (#83760) collapsed the two fix providers into one, and this pass extends the use-collection-initializer walk to also produce `InitializerMatchKind.MemberInitializer` matches when the language admits member-init folds.

The walk's per-statement try-order becomes member-init → Add/AddRange → index, with a shape-lock so pre-mixed-init languages don't interleave member and collection-element kinds and a duplicate-target rule (`seenNames`) preserved from the legacy member-init walk. All the member-init helpers (`IsExplicitlyImplemented`, `ImplicitMemberAccessWouldBeAffected`, the duplicate-target / shape-lock / compound-assignment-statement gates) move into the unified walk.

Two abstract hooks are added so the per-language concretes opt into the member-init behavior (`SupportsCompoundAssignmentInInitializer`, `SupportsMixedObjectAndCollectionInitializers`). C# returns the language-version-gated answer; VB returns false for both. Per-language concretes gain a new `TAssignmentStatementSyntax` generic slot threaded through the walk → diagnostic analyzer → fix provider chain.

`AbstractUseCollectionInitializerDiagnosticAnalyzer.AnalyzeNode` bails out when the merged match list contains any `MemberInitializer` entry; the use-object-initializer diagnostic analyzer remains the canonical reporter for those (it owns the IDE0017 / IDE0400 routing from PR 7). This prevents the two analyzers from double-reporting on mixed scenarios under the mixed object/collection initializer feature.

The pre-existing `AbstractUseNamedMemberInitializerAnalyzer` walk and `AbstractUseObjectInitializerDiagnosticAnalyzer` are intentionally left in place by this pass — they continue to handle pure-member-init scenarios on types without an accessible `Add` (where IDE0028's strict `ShouldAnalyze` rules out the walk entirely). A follow-on pass can delete those once IDE0028's `ShouldAnalyze` is loosened in lock-step with tightening the collection-expression synthesis path to skip problematic existing initializers.

Two correctness fixes the unified walk needs that pre-Pass-3 `ShouldAnalyze` short-circuiting handled implicitly:

  • Setup loop pre-populates `seenIndexAssignment` from element-access children of an existing `ObjectInitializerExpression` (`{ [k] = v }`) so iteration-2 fix verification doesn't falsely fold a subsequent `c.Add(…)`.
  • Per-statement Add detection is gated on `GetAddMethods().Any()` so explicit-interface-implemented `Add` methods (visible to `GetSymbolInfo` but not `LookupSymbols`) don't falsely match.

942 init-suite tests + 20620-test full Features suite both green; zero behavior changes user-visibly.

Stacked:

Microsoft Reviewers: Open in CodeFlow

Cyrus Najmabadi added 30 commits April 22, 2026 11:26
…nitializers

Accept any compound assignment operator (and `??=`) after the target of a
named member initializer (`Prop += 1`) or a dictionary member initializer
(`[key] += value`), producing the matching `AssignmentExpressionSyntax`
kind instead of unconditionally `SimpleAssignmentExpression`. The object
vs collection initializer classifier now recognizes any assignment whose
left is an `IdentifierName` or `ImplicitElementAccess`, so a brace list
containing only compound members still classifies as an object
initializer.

This makes the parser well-formed for the compound-assignment-in-
initializer feature (dotnet/csharplang#9896). Binding-time legality
checks (feature availability, target kind, duplicate rules, event-only
`+=`/`-=`, nested `{ ... }` rejection) are unchanged and still apply on
top of the new trees.
The lexer splits `>>=` and `>>>=` into multiple `>` / `>=` tokens so that
nested generic argument lists stay parseable. The expression parser
reconstructs the single token on demand (`EatExpressionOperatorToken`).
The member initializer operator path I added in the previous commit
missed that detail, so `new Foo { [0] >>= 1 }` and `new Foo { [0] >>>= 1 }`
emitted a spurious `'=' expected` diagnostic.

Factor the adjacency check into `IsSplitRightShiftAssignmentAt`, use it
in both `IsNamedMemberInitializer` (to trigger the named-member path for
`Prop >>= 1`) and `EatMemberInitializerOperatorToken` (to reconstruct
the merged token), and delegate to the expression parser's existing
`EatExpressionOperatorToken` for the actual consumption.
`CompoundAssignmentInitializerParsingTests` covers the parser changes
for dotnet/csharplang#9896:

  * All 11 compound operators (and `??=`, which the parser accepts as a
    permissive shape for the binder to reject) on named and indexer
    member initializers in object initializer and `with` expression
    contexts.
  * Parses cleanly at every language version from C# 1 through Preview
    with no parse diagnostics.
  * Object-vs-collection classifier: compound members with an identifier
    or implicit element access target classify as object initializer;
    compound members with other shapes (e.g. `a.b += 1`) do not.
  * Missing RHS, trailing comma, generic RHS expression, `ref` on RHS,
    nested `{ ... }` on RHS (permissive parse).
  * Colon recovery for the simple-assignment form remains intact.

Link `ParsingTests.cs` and `SyntaxExtensions.cs` from the Syntax test
project into the CSharp15 test project so the usual `UsingTree`,
`UsingExpression`, `N`, `M`, and `EOF` helpers are available here.

Update the baseline in `RefFieldParsingTests.ObjectInitializer_CompoundAssignment`
to match the new classification (the brace list now classifies as
`ObjectInitializerExpression` because any compound assignment with an
identifier target counts as object-initializer evidence) and to drop
the parser-time `ref`-on-RHS diagnostic, which is now the binder's
responsibility.
The assignment kind is derivable from the token's kind via
SyntaxFacts.GetAssignmentExpression, including the recovery paths where
the returned token is an EqualsToken. Simplify the helper to return a
single SyntaxToken and have each caller compute the kind at the
SyntaxFactory.AssignmentExpression call site.
Drops the hand-maintained AllLanguageVersions TheoryData in favor of
[CombinatorialData] on the LanguageVersion parameter, which iterates
every enum value (including the Default/Latest/LatestMajor aliases).
Also fixes a stray cref in the EatMemberInitializerOperatorToken doc
comment.
Converts every single-op test in CompoundAssignmentInitializerParsingTests
to a [Theory] over the existing CompoundOperators member data, so each
shape now gets exercised with all 11 compound operators plus ??=.
MissingRightHandSide computes the diagnostic column from the operator
length so <<= / >>= / >>>= / ??= trees all line up.

Test count grows from 94 to 226; runtime is still negligible since these
are pure parser-tree shape checks.
…ression

Implements the binding side of the compound-assignment-in-initializer-and-with
feature (csharplang#9896), building on the parser support already landed on
this branch.

* New MessageID.IDS_FeatureCompoundAssignmentInInitializer at LanguageVersion
  .Preview, checked on the operator token in BindInitializerMemberAssignment.
* BindInitializerMemberAssignment grows an additional arm that covers all 11
  compound_assignment_operator kinds. ??= is deliberately not included; the
  parser accepted it for resilience and the existing default fallthrough
  reports CS0747.
* BindObjectInitializerMember/BindObjectInitializerMemberCommon gain an
  overload that takes an explicit BindValueKind (so the compound path can
  request BindValueKind.CompoundAssignment) and exposes the unwrapped member
  access through an `out BoundExpression rawAccess` parameter.
* BindCompoundAssignment is split: a new internal BindCompoundAssignmentCore
  runs the operator-resolution body against pre-bound left/right. The
  ordinary expression wrapper still binds them up front.
* Event targets with `+=`/`-=` are detected via `rawAccess is BoundEventAccess`
  and dispatched directly to BindEventAssignment (which produces a
  BoundEventAssignmentOperator). Event targets with any other compound op
  fall through to BindCompoundAssignmentCore, which reports CS0019.
* ReportDuplicateObjectMemberInitializers is rewritten as a per-name
  None/SeenEquals/SeenCompound state machine to enforce the spec rule "at
  most one `=`, `=` must come before any compound, compound is unrestricted"
  for field/property targets. Event and indexer targets stay unrestricted.

No lowering changes yet; this commit makes `new T { Prop += 1 }` and
`r with { Value -= 1 }` bind to the expected BoundCompoundAssignmentOperator /
BoundEventAssignmentOperator shapes. Phase 3 will teach the object-initializer
lowerer to emit IL for the new bound-tree shapes.
…itializers

Pass the raw member access (BoundPropertyAccess / BoundFieldAccess /
BoundEventAccess / BoundIndexerAccess / BoundDynamicObjectInitializerMember
/ ...) to BindCompoundAssignmentCore instead of the BoundObjectInitializerMember
wrapper. CheckValueKind has no case for BoundObjectInitializerMember, so
shouldTryUserDefinedInstanceOperator was incorrectly returning false and
skipping user-defined `operator +=` resolution for any type that relied on
the in-place form.

The wrapper exists to drive simple-assignment lowering; for compound we
rely on the outer BoundObjectInitializerExpression.Placeholder being
registered for substitution at lowering time, which lets the raw access
(rooted at the placeholder) lower normally.

Also removes the explicit event dispatch in the compound branch; it now
falls out of BindCompoundAssignmentCore's existing `left.Kind == EventAccess`
check, which produces the same BoundEventAssignmentOperator.
…ject brace RHS

Three fixes to the compound-assignment-in-initializer binder path surfaced by
an adversarial review of the Phase 2a diff:

* Set WasPropertyBackingFieldAccessChecked on BoundPropertyAccess after the
  value-kind check in BindObjectInitializerMemberCommon so the post-bind
  walker in MethodCompiler does not assert on a compound-initializer's
  raw-access left (which is no longer wrapped in BoundObjectInitializerMember).
* When BindObjectInitializerMemberCommon reports a value-kind error (CS0200 on
  get-only, CS0154 on set-only, CS0191 on readonly, CS8331 on ref-readonly,
  and similar) the hasErrors flag is set on the wrapper but does not propagate
  to the raw access. Wrap the raw access in ToBadExpression in that case so
  downstream flow analysis / operator resolution sees a known-bad left.
* Reject InitializerExpressionSyntax as the RHS of a compound member
  initializer (`Prop += { 1, 2 }`) with ERR_InvalidInitializerElementInitializer
  before falling through; the previous break was routing the whole expression
  through BindValue -> BindCompoundAssignment -> BindValue on a brace-list
  kind that BindExpressionInternal does not handle, tripping a debug trace
  listener.
CompoundAssignmentInitializerBindingTests covers:

  * All 11 compound operators on property / field / indexer / with-record.
  * Target kinds: writable field, readonly field, get-only / set-only /
    init-only / ref-returning / ref-readonly properties, indexers (including
    dictionary-style multi-key), field-like and custom events, static
    members, dynamic-typed member.
  * Event target restricted to += / -=; other compound ops produce CS0019.
  * Event targets in a `with` expression on records.
  * Language version gating: Preview compiles; C# 14 (where user-defined
    `operator +=` is available but our feature is not) produces a clean
    feature-unavailable diagnostic on each of 11 compound operators.
  * `??=` in a member initializer is rejected.
  * Duplicate-rule permutations (= =, = +=, += =, += +=, = += =, record with).
  * Indexer and event targets are unrestricted for duplicates.
  * Compound RHS: ref rejected, nested brace-list rejected.
  * Containers: struct, record class, record struct, anonymous type.
  * User-defined operators: legacy `operator +` resolves on properties,
    direct `operator +=` resolves on fields (and wins over legacy when both
    are defined), in-place-only on properties fails because properties are
    not variables.
  * `required` members: compound alone does not satisfy the obligation;
    `= 0, += 1` does (matches the "No" recommendation in the LDM question
    added to dotnet/csharplang#10132).

Update four existing UnsignedRightShiftTests baselines. The classifier
change from Phase 1 makes `new List<int> { x >>= 1 }` classify as an
object initializer (`x` as a named member) rather than a collection
initializer, so the diagnostic shifts from CS0747 to CS0117 (no member
named `x`). The new behavior is consistent with the feature.
…t path

The non-compound path wraps BoundPropertyAccess inside BoundObjectInitializerMember,
and the walker in MethodCompiler never inspects the wrapped access, so the debug
flag is only needed when the raw access is handed out unwrapped to
BindCompoundAssignmentCore. Gate on the valueKind the compound branch already
passes in (CompoundAssignment) rather than setting the flag unconditionally.
- Add enum target coverage: flag-enum bitwise (|=/&=/^=), plain-enum += with
  an int literal, enum+enum rejection, *= rejection, flag-enum in `with`,
  and the mixed `= then |= then &=` rule on an enum property.
- Drop explicit `parseOptions: TestOptions.RegularPreview` from happy-path
  tests (preview is the default) and remove the now-redundant
  LangVersion_Preview_Compiles; LangVersion gating stays on the CS13/CS14
  tests.
- Drop `targetFramework: TargetFramework.NetCoreApp`; bundle a single
  `Polyfills` source (IsExternalInit, CompilerFeatureRequiredAttribute,
  Required/SetsRequiredMembersAttribute from CSharpTestBase) into every
  compilation so individual tests don't need to pick a framework.
`BindObjectInitializerMemberAccess` returns the raw bound member access;
`WrapAsObjectInitializerMember` wraps it in `BoundObjectInitializerMember` for
callers that need the simple-assignment lowering shape. Removes the dual-return
(wrapped + `out rawAccess`) contortion on `BindObjectInitializerMember`, and
makes the compound-assignment path's need for the raw access an explicit
structural choice at the call site rather than a side channel.

Simple-assignment and missing-assignment callers wrap; the compound-assignment
branch uses the raw access directly (unchanged behavior).

Also drops a redundant CS1918 that the binder produced on `new C { P += { 1, 2 } }`
as a side effect of treating the compound RHS as a nested initializer for the
property-nested-initializer check. `isRhsNestedInitializer` is now unconditionally
false on the compound path (compound initializer members cannot take a nested
initializer; that's CS0747 before anything else runs), so only CS0747 fires.
`LocalRewriter.AddObjectInitializers` hard-cast every initializer to
`BoundAssignmentOperator`, so `new C { P += v }`, `with { P -= v }`, or
`new C { E += h }` would crash lowering with `InvalidCastException`. The binder
tests passed only because `VerifyDiagnostics()` stops before lowering.

Changes:
- Binder: after `BindCompoundAssignmentCore`, swap the raw access in
  `BoundCompoundAssignmentOperator.Left` for a `BoundObjectInitializerMember`
  wrapper via `.Update(...)`. `LeftConversion` references `LeftPlaceholder` (not
  `Left`), so the swap is safe. Events stay as `BoundEventAssignmentOperator`
  with a placeholder `ReceiverOpt` (no `Left` to wrap). Drops the debug-only
  `WasPropertyBackingFieldAccessChecked` workaround — no longer needed once the
  wrapper hides `BoundPropertyAccess` on the compound path too.
- Lowering: replace the hard-cast in `AddObjectInitializers` with a `Kind`
  switch. `BoundAssignmentOperator` takes the existing path; new
  `AddCompoundObjectInitializer` reuses `VisitObjectInitializerMember` +
  `MakeObjectInitializerMemberAccess` to substitute the placeholder receiver,
  rebuilds the compound op with the real access as `Left`, and hands it to the
  general compound-assignment lowering to emit the read-op-write sequence;
  `AddEventObjectInitializer` substitutes the placeholder in `ReceiverOpt` and
  hands to `VisitEventAssignmentOperator` to emit the add_/remove_ call.
- `NullableWalker.VisitObjectInitializerMember` now performs a reasonable visit
  instead of throwing `Unreachable`. The simple-assignment path still dispatches
  inline via `VisitObjectElementInitializer`; the compound path reaches us via
  `VisitCompoundAssignmentOperator.Visit(Left)` and needs a real result.
- `GetLValueAnnotations` delegates to the wrapped member symbol for
  `BoundObjectInitializerMember` (property and field cases).

Minor behavior change: for a binding-error case like `new C { E *= h }` where
`E` is an event, the event's associated-field read is no longer tracked through
the wrapper, so `WRN_UnreferencedEvent` fires alongside the primary
`ERR_BadBinaryOps`. Test baseline updated to expect both.
New `CompoundAssignmentInitializerEmitTests`:
- For every one of the 11 compound operators, on property / field / indexer
  targets, assert that `new C { P op= rhs }` produces the same final state as
  `var c = new C(); c.P op= rhs;` via a CompileAndVerify smoke test.
- `with` expression on a record property.
- Event `+=` / `-=` in an object initializer, running and verifying handler
  invocation.
- User-defined in-place `operator +=` on a struct field target.
- Flag enum `|=` (starting from a non-zero default) and mixed `= ... , &= ~X`.
- IL verification for three representative cases: property `+=`, indexer `|=`,
  and event `+=`.

New binder test `ExpressionTree_CompoundMemberInitializer_Fails` pins the
`ERR_ExpressionTreeContainsAssignment` error path, confirming the diagnostics
pass catches compound member initializers before `ExpressionLambdaRewriter`'s
hard-cast could fire.
Rework BindInitializerMemberAssignment so the compound branch mirrors the
simple-assignment branch: bind the left via BindObjectInitializerMember, bind
the right, dispatch to BindCompoundAssignmentCore (just like the normal
`a.b += c` path hands left and right to BindCompoundAssignmentCore). Drop the
BindObjectInitializerMemberAccess + WrapAsObjectInitializerMember split and the
post-bind Update swap.

BindObjectInitializerMember pre-validates the target with
BindValueKind.CompoundAssignment when the member initializer is compound, so
set-only properties and readonly fields error at bind-member time (just like
they do for `a.b += c`). BindCompoundAssignmentCore sees the wrapped
BoundObjectInitializerMember and needs two small accommodations:

- CheckValueKind grows a BoundObjectInitializerMember case. For non-RefersToLocation
  queries the pre-validation result carries (wrapper never errors here), and
  for RefersToLocation queries (the gate in shouldTryUserDefinedInstanceOperator
  that decides whether user-defined in-place `operator +=` is applicable) it
  dispatches on the wrapped MemberSymbol: non-readonly fields and ref-returning
  properties are ref-assignable locations, regular properties, indexers, and
  events are not. This matches the normal `a.b += c` outcome.
- The event `+=` / `-=` dispatch in BindCompoundAssignmentCore also matches
  `BoundObjectInitializerMember { MemberSymbol: EventSymbol }`. It synthesizes
  a BoundEventAccess with a placeholder receiver (lowering substitutes it in
  AddEventObjectInitializer) and hands to BindEventAssignment as usual, so
  `new C { E += h }` and `c with { E += h }` lower to add_E accessor calls
  rather than bypassing the accessor with a ldfld/Combine/stfld sequence.

Also hoist the nested-initializer rejection above RHS binding in the compound
branch so `new C { P += { 1, 2 } }` reports CS0747 alone rather than also
emitting CS1918 / CS1922 for the brace-list RHS. Updates the RefOnRhs_Rejected
baseline from CS1073 (parser-level "unexpected 'ref'") to CS8373
(ERR_RefLocalOrParamExpected) — the new diagnostic is more specific and
mirrors what `c.P = ref x` produces outside an initializer.

80k+ existing tests across Semantic / Symbol / Emit / Emit3 / IOperation
remain green.
…d RHS

Move the `compound + nested-initializer RHS` rejection from above the left /
right bind to below it. Binding both sides first lets the user see each
individual diagnostic (CS1918 on the nested-initializer-into-value-type target,
CS1922 on the brace-list-into-non-IEnumerable RHS), and the compound-specific
CS0747 is added as a summary. Returns a `BoundBadExpression` whose children are
the bound left and right so semantic-model / IDE consumers still see what each
side resolved to.
`ReportDuplicateObjectMemberInitializers` was using a three-value enum but only
ever cared about "have we seen any initializer for this member?". Collapse to
`PooledHashSet<string>` — a second `=` (or a `=` following a compound) is the
only error, detected by `Add` returning false.

Expand event-target coverage:
- `Event_NonPlusOrMinusCompound_Fails` is now a theory over all 9 non-+/-
  compound operators (`*=`, `/=`, `%=`, `&=`, `|=`, `^=`, `<<=`, `>>=`, `>>>=`);
  each produces CS0019 via fall-through to binary-operator overload resolution
  on the delegate type.
- Add `Event_SimpleAssignment_FromInsideContainingType_Succeeds`: Roslyn's
  long-standing field-like-event spec violation (treating `E = h` as write to
  the backing field when accessed from inside the declaring type) still works
  in initializer context.
- Add `Event_SimpleAssignment_FromOutsideContainingType_Fails`: from outside
  the declaring type, `E = h` errors with CS0070 just like `c.E = h` would —
  the object-initializer context doesn't relax the check.
- Add `Event_SimpleAssignment_CustomEvent_Fails`: a custom event (explicit
  add/remove accessors) has no backing field, so `E = h` fails with CS0079
  even from inside the declaring type.
…elpers

The wrapper's previous simplified value-kind logic duplicated bits of
CheckFieldValueKind / CheckPropertyValueKind and got details wrong: it accepted
`ref readonly` properties as ref-assignable (they aren't — CS8331 should fire),
and it ignored ref-readonly fields, fixed-size-buffer fields, and value-type
receiver modifiability rules.

Reconstruct a raw BoundFieldAccess / BoundPropertyAccess / BoundIndexerAccess /
BoundEventAccess from the wrapper's MemberSymbol + arguments + a placeholder
receiver, and hand it to CheckValueKind. The existing per-kind helpers then
apply the full check for free.

Tests added:
- `UserDefined_InPlaceOnly_OnRefReturningProperty_Succeeds`: ref-returning
  property is a location; in-place `operator +=` applies. (Was previously
  un-covered.)
- `UserDefined_InPlaceOnly_OnRefReadonlyProperty_Fails`: `ref readonly`
  property is a location but not ref-assignable; compound initializer emits
  CS8331. (Would previously have incorrectly let the in-place operator run.)
…entAssignment

BindEventAssignment only reads three things from its BoundEventAccess argument:
EventSymbol, ReceiverOpt, and the delegate Type. Refactor it to take those
directly, so the wrapper-event case in BindCompoundAssignmentCore doesn't need
to allocate a BoundEventAccess just to have it unpacked immediately. Tuple-
switch at the call site extracts the three pieces from either a raw
BoundEventAccess or a BoundObjectInitializerMember whose MemberSymbol is an
EventSymbol.
…tAssignment

The tuple-switch that unpacks either a BoundEventAccess or a
BoundObjectInitializerMember-wrapped event and dispatches to BindEventAssignment
was inline. Lift it to a named helper so the outer local function stays flat
and the event-path invariants live in one place.
…rage

- AddCompoundObjectInitializer now dispatches over every Left shape
  BindObjectInitializerMemberCommon can produce (BoundObjectInitializerMember,
  BoundImplicitIndexerAccess, BoundArrayAccess, BoundPointerElementAccess,
  BoundDynamicObjectInitializerMember) rather than hard-casting to the wrapper,
  closing two reachable crashes (Index/Range-indexer, dynamic nested initializer).

- NullableWalker.VisitObjectCreationInitializer's inner switch now routes
  BoundKind.CompoundAssignmentOperator (and BoundKind.NullCoalescingAssignment-
  Operator) through a new slot-tracking helper that mirrors VisitObjectElement-
  Initializer. Without this, the container's per-member nullable state was stale
  after a compound member initializer.

- BindInitializerMemberAssignment's switch now admits CoalesceAssignmentExpression
  alongside the eleven regular compound operators. Binder_Operators extracts
  BindNullCoalescingAssignmentOperatorCore (parallel to BindCompoundAssignment-
  Core) so the initializer path can supply a pre-bound Left validated with
  BindValueKind.CompoundAssignment. Lowering shares a RewriteInitializerMember-
  LeftOperand dispatcher across compound and ??= paths; NullableWalker shares
  UpdateInitializerMemberSlot.

- The spec diff targeted ECMA-334 v7, whose assignment_operator production
  pre-dates ??=. The C# 8 null-coalescing-assignment proposal defines ??= to
  follow compound-assignment semantic rules, so we group it with
  compound_assignment_operator in the initializer admission here; the separate
  csharplang PR updates the proposal text.

- 22 new tests: crash regressions for the three bugs above, plus coverage the
  original adversarial audit flagged as missing (semantic model / IOperation,
  indexer args evaluated exactly once, checked / unchecked propagation, string
  compound, nullable flow through the LHS, primary-constructor positional
  property in with), plus a full Coalesce_* region pinning ??= behavior across
  nullable value types, reference types, with expressions, duplicate rules,
  required members, the language-version gate, and the event corner cases.
On a freshly constructed C, a field-like event is guaranteed null. `??= h`
reduces to an unconditional write, which on a null event field is functionally
equivalent to `+= h` (subscribe via add_E) — legal from outside C per the event
spec. So this test pinning clean compilation documents correct behavior, not a
hole in binding.
When a constructor carries [SetsRequiredMembers], required-member enforcement
is lifted for that new-expression, so the initializer is free to read-modify-
write the pre-initialized value. Counterpart to Required_CompoundAlone_DoesNotSatisfy.
Exercises the interaction of record struct copy semantics (`with` clones) +
ref-returning property ([UnscopedRef] into the clone's field) + user-defined
in-place `operator +=` on a struct (mutates via the ref). Pins that the clone
sees the bump while the original stays untouched.
Cyrus Najmabadi added 18 commits April 28, 2026 12:30
… carve-out

Audit follow-up against the now-merged spec (csharplang#10151 + dotnet#10152):

* `Binder_Expressions.ReportDuplicateObjectMemberInitializers` — SPEC
  comment block updated to match the merged paragraph: "any number of
  member initializers using a compound assignment operator are permitted
  for the same target. If present, an `=` member initializer shall
  appear in lexical order before any other member initializer for that
  target. No such restriction applies to indexer targets." Inner comment
  next to the duplicate-fires guard restated to the same vocabulary.
* `Binder_Expressions.BindObjectInitializerExpression` — companion
  comment on the `memberNameMap` allocation updated to the same phrasing.
* `Duplicate_Indexer_Unrestricted` test comment — drop the stale "event
  or indexer" wording (spec only carves out indexer targets).
* `Duplicate_Indexer_FirstForm_SameKeyUnrestricted` (new test) — pin
  the spec's deliberate exclusion of indexer targets from the first-form
  exclusivity rule. Two `[k] = { … }` initializers for the same indexer
  key are permitted; both invoke the indexer getter and configure the
  returned instance.
…lizers

The "mixed object and collection initializers" feature (dotnet/csharplang#10185)
relaxes binding so that a single `{ ... }` initializer body may contain both
member-shaped initializer elements (`Name = value`, `Name op= value`,
`[args] = value`) and bare-expression element initializers (`Add` targets).

At the parser level no change is needed: `ParseObjectOrCollectionInitializer`
already accepts mixed-shape lists, and its classifier flips the wrapper to
`ObjectInitializerExpression` whenever at least one element is an
`AssignmentExpressionSyntax` whose `Left.Kind` is `IdentifierName` or
`ImplicitElementAccess`, otherwise to `CollectionInitializerExpression`. The
binder continues to reject mixed lists at every language version until the
binding PR flips the feature gate.

This PR adds `MixedInitializerParsingTests.cs` to pin that classification
behavior so subsequent PRs cannot silently regress it. Coverage includes:

  * Sanity invariants (empty / pure members / pure elements).
  * Minimum-shape "flips" (one qualifying element is enough, leading or trailing).
  * Mixed orderings (members-first, elements-first, interleaved).
  * Simple and compound member shapes (`Name =`, `Name op=`, `[args] =`,
    `[args] op=`) theorized over every compound assignment operator.
  * Brace-list element initializer, nested object-creation, nested
    `target = { ... }` first-form initializer alongside top-level elements.
  * Classifier boundary cases pinning that qualifier-style assignments
    (`a.b op= …`) do not contribute to the wrapper flip in a mixed list.
  * `new T(args) { ... }` and target-typed `new() { ... }` forms.
  * Trailing comma and `;` separator.
  * Colon recovery (`X: 1` → `SimpleAssignmentExpression`) still qualifies.
  * `with { ... }` parser-permissive shape (out of feature scope; pinned to
    catch incidental regressions).
  * Parses cleanly at every language version (binder gates the feature later).
…and ET rejection

Feature-gated end-to-end implementation of dotnet/csharplang#10185. Under
LanguageVersion.Preview, an `ObjectInitializerExpression` may now contain
both member-shape initializer elements (`Name = value`, `Name op= value`,
`[args] = value`) and bare-expression element initializers (`Add` targets).
Each child binds, lowers, flows, and emits per its own kind's existing
rules; the binder dispatches per-element in `BindObjectInitializerExpression`,
the lowering switch in `LocalRewriter.AddObjectInitializers` gains
`CollectionElementInitializer` and `DynamicCollectionElementInitializer`
arms, and `NullableWalker.VisitObjectCreationInitializer`'s object branch
delegates collection-element-shape children to `VisitCollectionElementInitializer`.
The `IEnumerable` + `Add`-resolution requirement is computed lazily, so
pure-member object initializers are unaffected.

`with` expressions stay out of feature scope: the dispatch is gated on
`isObjectInit`, so `WithInitializerExpression` continues to reject any
non-member child via the existing `BindInitializerMemberAssignment` path.

`required` member discharge is unchanged in semantics: the existing
`CheckRequiredMembersInObjectInitializer` already skips non-`BoundAssignmentOperator`
entries, so `element_initializer`s correctly do not satisfy `required`.

The mixed shape is explicitly rejected inside `Expression<>` lambdas via a
new `ERR_ExpressionTreeContainsMixedObjectAndCollectionInitializer` reported
from `DiagnosticsPass_ExpressionTrees.VisitObjectInitializerExpression` —
the `System.Linq.Expressions.MemberInit` shape has no representation for
"Add this value alongside the member assignments".

Pre-feature behavior is preserved at sub-preview language versions: the
new dispatch is gated on `Compilation.IsFeatureEnabled`, so the existing
`ERR_InvalidInitializerElementInitializer` / recovery path continues to
fire unchanged at C# 14 and below. Eight existing tests across
`ObjectAndCollectionInitializerTests`, `IOperationTests_IObjectCreationExpression`,
`SemanticErrorTests`, and `RefFieldTests` are pinned at `TestOptions.Regular14`
since they assert the pre-feature diagnostic shape.

Adds two new CSharp15 test files:

  * `MixedInitializerBindingTests.cs` — 24 tests covering members + elements
    in all orderings, simple/compound/indexer/event/null-coalescing members,
    multi-arg `{ a, b }` Add, extension `Add`, target-typed `new()`,
    ctor-args form, `init` members, dynamic `Add` arguments, dictionary
    indexer, generic `Add` overload, type-without-`IEnumerable` /
    type-without-`Add` rejection, `required` member behavior (pre-PR
    behavior preserved), `with` regression pin, expression-tree rejection,
    duplicate-member rule preservation, and language-version gating.

  * `MixedInitializerEmitTests.cs` — 7 runtime/IL tests pinning lexical
    ordering across `Add` calls and member writes (the headline observable
    behavior), compound/indexer/event/brace-list interactions, and a
    canonical IL shape.
Updates the shared workspace/IDE-foundation helpers that pattern-match on
`SyntaxKind.ObjectInitializerExpression` / `CollectionInitializerExpression`
so the mixed `{ ... }` shape (`ObjectInitializerExpression` with element-shape
children, dotnet/csharplang#10185) is treated correctly by downstream IDE
features without per-feature changes:

  * `FormattingHelpers.IsInitializerForObjectOrAnonymousObjectCreationExpression`
    now keys off the parser-assigned wrapper kind instead of a "first element
    is `AssignmentExpressionSyntax`" heuristic, which mis-classified mixed
    initializers whose first element happens to be a bare expression
    (e.g. `new C { 1, X = 2 }`).
  * `SpeculationAnalyzer` gains an `ObjectInitializerExpression` arm that
    routes element-shape children through the same
    `ReplacementBreaksCollectionInitializerAddMethod` guard the pure
    collection initializer arm has, so speculative replacement of an
    element inside a mixed wrapper can't silently change `Add` resolution.
  * `UseUtf8StringLiteralDiagnosticAnalyzer` widened its parent-kind pattern
    to also recognize `ObjectInitializerExpression` parents, so a
    `params byte[]`-Add invocation inside a mixed wrapper still surfaces the
    UTF-8 literal suggestion.

Adds two regression tests pinning the new behavior — one in
`FormattingTests.ObjectInitializer_MixedObjectAndCollection_ElementFirst` and
one in `UseUtf8StringLiteralTests.TestMixedObjectAndCollectionInitializer`.

Deliberately deferred:

  * `CSharpSemanticModel.GetCollectionInitializerSymbolInfo` widening to
    accept element-shape children whose parent is `ObjectInitializerExpression`
    — naturally lands with the SemanticModel/IOperation PR.
  * `AbstractReferenceFinder.FindReferencesInCollectionInitializer` +
    `SyntaxTreeIndex_Create.containsCollectionInitializer` widenings — depend
    on the above compiler API change.
  * `UseInitializerHelpers.GetNewObjectCreation` first-element-only wrapper-kind
    selection — lands with the IDE0017/IDE0028 unification PR.
  * `CSharpAddImportFeatureService` collection-only gates — lands with the IDE
    polish PR.
… IOperation tests

Widens `CSharpSemanticModel.GetCollectionInitializerSymbolInfo` (and the
matching `MemberSemanticModel` worker) to accept element-shape children
whose parent is the mixed `ObjectInitializerExpression` wrapper
(dotnet/csharplang#10185), so the public API returns the `Add` symbol info
for those children the same way it does for pure-collection wrappers.

The downstream `AbstractReferenceFinder.FindReferencesInCollectionInitializer`
+ `SyntaxTreeIndex_Create.containsCollectionInitializer` consumers are also
widened to walk mixed wrappers; `CSharpTypeInferenceService.TypeInferrer`'s
element-type inference arm is widened to also fire on non-assignment-shape
children of `ObjectInitializerExpression`.

Adds eight tests in `MixedInitializerSemanticModelTests.cs` covering:
`GetCollectionInitializerSymbolInfo` on element-shape children of mixed
wrappers, member-shape children (returns `SymbolInfo.None`), pure-collection
parity, nested-mixed (`Prop = { mixed }`), `ComplexElementInitializer` /
multi-arg `Add` in a mixed wrapper, and the IOperation tree shape (unified
`IObjectOrCollectionInitializerOperation` with mixed `IAssignmentOperation`
+ `IInvocationOperation` / `IDynamicInvocationOperation` children, in
lexical order). Adds one parallel `TypeInferrerTests` pin (EditorFeatures,
Windows-only at runtime).

Deferred (per PR plan):
  * `UseInitializerHelpers.GetNewObjectCreation` first-element-only
    wrapper-kind selection — lands with the IDE0017/IDE0028 unification PR.
  * `CSharpAddImportFeatureService` collection-only gates — lands with the
    IDE polish PR.
  * `SpeculationAnalyzer` + `TypeInferrer` position-only-cursor case —
    EditorFeatures-only tests; will pin in a follow-up that doesn't depend
    on the Windows test surface.
Extends the UseObjectInitializer (IDE0017) analyzer so subsequent
`instance.Add(value)` expression-statements fold into an existing object
initializer as bare-element initializers alongside member-shape children,
producing `new C { X = 1, 10, 20 }` for the mixed object/collection
initializer feature. Gated on `SupportsMixedObjectAndCollectionInitializers`
(C# Preview+, VB always false) and on the target type implementing
`IEnumerable` (mirrors IDE0028's precondition). Also fixes
`UseInitializerHelpers.GetNewObjectCreation` to pick the wrapper kind by
scanning all expressions rather than only the first, so the generated
wrapper matches the parser's classification for mixed lists.

Stacked:
* #83XXX mixed-init-syntax-facts (parser tests)
* #83XXX mixed-init-bind (binding + lowering + flow + emit + ET)
* #83XXX mixed-init-ide-foundation (formatter + speculation + UseUtf8)
* #83XXX mixed-init-semmodel (SemanticModel + find-refs + IOperation)
* >>> this PR <<<
…e carve-outs

Two remaining workspace/feature call sites still treated `CollectionInitializerExpression`
as the only valid wrapper for collection-element shapes; widens both to recognize element-
shape children of `ObjectInitializerExpression` (the mixed wrapper kind chosen by the
parser when any child is assignment-shape):

* `CSharpAddImportFeatureService` — `IsAddMethodContext` and the CS7036/CS0308/CS0428/CS1061
  error-recovery arm route through a new `IsCollectionElementInitializerContext` helper that
  accepts either `CollectionInitializerExpression` parents or non-assignment children of
  `ObjectInitializerExpression`.
* `ExpressionSyntaxExtensions.CanReplaceWithLValue` — adds a guarded
  `ObjectInitializerExpression` case (excluding `AssignmentExpressionSyntax` children, which
  must remain non-replaceable because they bind as member initializers).

Adds tests covering both widenings, including a negative IntroduceVariable test that pins
the member-name LHS of a mixed init as non-replaceable, and parses the C# SpeculationAnalyzer
test base under `LanguageVersion.Preview` so the mixed-init speculation case binds.

Stacked:
* dotnet#83750 mixed-init-syntax-facts (parser tests)
* dotnet#83751 mixed-init-bind (binding + lowering + flow + emit + ET)
* dotnet#83752 mixed-init-ide-foundation (formatter + speculation + UseUtf8)
* dotnet#83753 mixed-init-semmodel (SemanticModel + find-refs + IOperation)
* dotnet#83754 mixed-init-ide0017-extension (IDE0017 Add-fold extension)
* >>> this PR <<<
…ia new IDE0400

Introduces IDE0400 (UseMixedObjectAndCollectionInitializer) and refactors the
UseObjectInitializer analyzer to classify-and-route based on the shape of the
synthesized initializer rather than always reporting IDE0017:

  * Pure member-shape synthesis (existing behavior) -> IDE0017.
  * Pure Add-shape synthesis with no existing member init -> no report; IDE0028
    is the canonical owner of that shape and continues to fire unchanged.
  * Mixed synthesis (member + Add across existing initializer + new matches) ->
    IDE0400 (new). Notification severity = the lesser of PreferObjectInitializer
    and PreferCollectionInitializer; mixed is suppressed entirely when either
    preference is disabled.

This eliminates the duplicate IDE0017+IDE0028 squiggle introduced in PR 5 on
Preview pure-Add sequences (`var c = new C(); c.Add(1); c.Add(2);`), and gives
mixed sequences a distinct diagnostic ID for severity and suppression. IDE0028
analyzer is untouched. Pre-Preview behavior is preserved unchanged.

Adds:
  * IDE0400 to IDEDiagnosticIds, EnforceOnBuildValues, analyzer resources, code
    cleanup and configure-severity tables, and the rules-missing-documentation
    index.
  * `MixedTest` test wrapper that maps `[|...|]` markup defaults to IDE0400 for
    tests whose synthesized initializer is mixed; the 5 mixed-init tests from
    PR 5 now go through it.
  * New IDE0028 test pinning that pure-Add at LanguageVersion.Preview still
    reports IDE0028 (and only IDE0028) after the unification.
  * UseObjectInitializer regression tests: IDE0017 still fires on pure-member
    under Preview, IDE0400 is suppressed when PreferCollectionInitializer is
    false, pure-Add yields silently.
  * Differentiated code-fix title: lightbulb for IDE0400 reads "Object and
    collection initialization can be merged" rather than the legacy IDE0017
    string.

Stacked:
* dotnet#83750 mixed-init-syntax-facts (parser tests)
* dotnet#83751 mixed-init-bind (binding + lowering + flow + emit + ET)
* dotnet#83752 mixed-init-ide-foundation (formatter + speculation + UseUtf8)
* dotnet#83753 mixed-init-semmodel (SemanticModel + find-refs + IOperation)
* dotnet#83754 mixed-init-ide0017-extension (IDE0017 Add-fold extension)
* dotnet#83755 mixed-init-ide-polish (AddImport + CanReplaceWithLValue carve-outs)
* >>> this PR <<<
…cation (shared match shape)

First of three planned passes that flatten the use-object-initializer (IDE0017)
and use-collection-initializer (IDE0028) analyzer families down to a single
walk + single fixer + single diagnostic analyzer. This pass changes only the
data shape that flows through both pipelines; behavior is identical on every
input. Passes 2 and 3 (merge fixers, merge analyzers) build on this.

Introduces `InitializerMatch<TNode>` (new) as the union of fields both walks'
match types carried before:

  * `Node` (was `Statement` / `CollectionMatch.Node`) — the fold-candidate
    syntactic node.
  * `Kind` (new) — `InitializerMatchKind` discriminator covering
    MemberInitializer, AddInvocation, IndexAssignment, ForEach, and the
    collection-expression-only ConstructorArgument shape.
  * `UseSpread` / `UseCast` / `UseKeyValue` — inherited from `CollectionMatch`,
    used by collection-expression synthesis only; member-init matches leave
    them at their defaults.

Both walks and both per-language fixers are migrated to consume this shape:

  * IDE0017's `AbstractUseNamedMemberInitializerAnalyzer` and the C#/VB
    `UseObjectInitializerCodeFixProvider` recover rich member-access /
    initializer data from the statement at fix time (no longer carried in the
    match), guarded by a `Kind`-driven dispatch with `ExceptionUtilities.UnexpectedValue`
    for unknown kinds.
  * IDE0028's `AbstractUseCollectionInitializerAnalyzer` and the C#/VB
    `UseCollectionInitializerCodeFixProvider` produce `InitializerMatch`
    instead of `CollectionMatch`; the collection-expression rewriter (shared
    with the IDE0300+ family that's NOT part of this unification) keeps its
    `CollectionMatch` signature and the IDE0028 fixer translates at the
    boundary.
  * `UseCollectionInitializerHelpers.GetLocationsToFade` gains an
    `InitializerMatch` overload sharing a node-based core with the existing
    `CollectionMatch` overload — the IDE0300+ analyzers use the legacy form
    and are unaffected.

Tests: 942 tests pass across IDE0017, IDE0028, and the IDE0300-IDE0306
collection-expression family with zero behavior changes; 20620-test full
Features suite also green.

Stacked:
* dotnet#83750 mixed-init-syntax-facts (parser tests)
* dotnet#83751 mixed-init-bind (binding + lowering + flow + emit + ET)
* dotnet#83752 mixed-init-ide-foundation (formatter + speculation + UseUtf8)
* dotnet#83753 mixed-init-semmodel (SemanticModel + find-refs + IOperation)
* dotnet#83754 mixed-init-ide0017-extension (IDE0017 Add-fold extension)
* dotnet#83755 mixed-init-ide-polish (AddImport + CanReplaceWithLValue carve-outs)
* dotnet#83757 mixed-init-unify-analyzers (IDE0017+IDE0028 routing + IDE0400)
* >>> this PR <<<
…cation (merged fix providers)

Second of three planned passes that flatten the use-object-initializer (IDE0017)
and use-collection-initializer (IDE0028) analyzer families. Pass 1 (dotnet#83758)
unified the match data shape; this pass collapses the two per-language fix
provider classes into one. The two walks remain separate at this layer —
Pass 3 collapses them into a single walk.

Introduces `AbstractUseInitializerCodeFixProvider<…>` (12 type parameters,
covering the union of both legacy abstract bases). One per-language concrete
each: `CSharpUseInitializerCodeFixProvider` and
`VisualBasicUseInitializerCodeFixProvider`. Each registers for IDE0017,
IDE0028, AND IDE0400 in a single `FixableDiagnosticIds` list, and dispatches
inside `FixAsync` based on a property-bag tag set by the analyzer:

  * `UseMemberInitializerName` (new) → member-init synthesis (IDE0017/IDE0400).
  * `UseCollectionExpressionName` (existing) → collection-expression synthesis.
  * Neither → collection-init synthesis.

The property-bag tag is needed because `ForkingSyntaxEditorBasedCodeFixProvider.FixAsync`
only receives the diagnostic's properties (not the diagnostic itself), and
under the mixed object/collection initializer feature both walks can return
non-empty matches for the same object creation — probing alone would mis-
route IDE0028 fix requests on Preview. The use-object-initializer analyzer
now attaches the `UseMemberInitializerName` property when reporting any of
its three IDs; fade-out diagnostics receive the same property so fix-all
across both primary and faded diagnostics dispatches consistently.

Deletes:
  * `AbstractUseObjectInitializerCodeFixProvider` and
    `AbstractUseCollectionInitializerCodeFixProvider` (replaced by the new
    unified abstract base).
  * `CSharpUseObjectInitializerCodeFixProvider`, `CSharpUseCollectionInitializerCodeFixProvider`,
    `VisualBasicUseObjectInitializerCodeFixProvider`,
    `VisualBasicUseCollectionInitializerCodeFixProvider` (replaced by the
    unified per-language concretes). The existing C# partials
    (`_CollectionInitializer.cs` and `_CollectionExpression.cs`) are
    re-namespaced and re-declared as partials of the new class; their
    static helpers carry over verbatim.

Per-language MEF registration uses `PredefinedCodeFixProviderNames.UseCollectionInitializer`
for the unified provider — the previous `UseObjectInitializer` constant is
unused but kept (no public API removal). Tests, telemetry table, and
projitems updated.

Tests: 942 init-suite tests + 20620-test full Features suite both green;
zero behavior changes.

Stacked:
* dotnet#83750 mixed-init-syntax-facts (parser tests)
* dotnet#83751 mixed-init-bind (binding + lowering + flow + emit + ET)
* dotnet#83752 mixed-init-ide-foundation (formatter + speculation + UseUtf8)
* dotnet#83753 mixed-init-semmodel (SemanticModel + find-refs + IOperation)
* dotnet#83754 mixed-init-ide0017-extension (IDE0017 Add-fold extension)
* dotnet#83755 mixed-init-ide-polish (AddImport + CanReplaceWithLValue carve-outs)
* dotnet#83757 mixed-init-unify-analyzers (IDE0017+IDE0028 routing + IDE0400)
* dotnet#83758 mixed-init-unify-pass1-match-shape (Pass 1: shared match shape)
* >>> this PR — Pass 2/3 of full unification <<<

Pass 3 (merge walks + delete duplicate diagnostic analyzers) follows as a
separate stacked PR.
…ication (merged walk)

Third pass of the IDE0017+IDE0028 unification — Pass 1 (dotnet#83758) unified the
match data shape, Pass 2 (dotnet#83760) collapsed the two fix providers into one,
and this pass extends the use-collection-initializer walk
(`AbstractUseCollectionInitializerAnalyzer`) to also produce
`InitializerMatchKind.MemberInitializer` matches when the language admits
member-init folds. The walk's per-statement try-order becomes member-init →
Add/AddRange → index, with a shape-lock so pre-mixed-init languages don't
interleave member and collection-element kinds and a duplicate-target rule
(`seenNames`) preserved from the legacy `AbstractUseNamedMemberInitializer`
walk. All the member-init helpers (`IsExplicitlyImplemented`,
`ImplicitMemberAccessWouldBeAffected`, the duplicate-target / shape-lock /
compound-assignment-statement gates) move into the unified walk.

Two abstract hooks are added so the per-language concretes opt into the
member-init behavior: `SupportsCompoundAssignmentInInitializer` (already
mirrored on the now-secondary `AbstractUseNamedMemberInitializerAnalyzer`)
and `SupportsMixedObjectAndCollectionInitializers`. C# returns the
language-version-gated answer; VB returns false for both.

The setup loop now also pre-populates `seenIndexAssignment` from
element-access children of an existing `ObjectInitializerExpression`
(`{ [k] = v }`) so subsequent `c.Add(…)` statements don't falsely match
through the unified walk on iteration-2 fix verification — pre-Pass-3 the
legacy `HasExistingInvalidInitializerForCollection` precondition short-
circuited those shapes via `ShouldAnalyze`, but the unified walk has to
preserve the invariant explicitly. Per-statement Add detection is gated on
`GetAddMethods().Any()` for the same reason — explicit-interface-implemented
`Add` methods bind via `GetSymbolInfo` but aren't found by `LookupSymbols`,
and without the gate they would falsely match.

`AbstractUseCollectionInitializerDiagnosticAnalyzer.AnalyzeNode` bails out
when the merged match list contains any `MemberInitializer` entry; the
use-object-initializer diagnostic analyzer remains the canonical reporter
for member-init-bearing match lists (it already owns the IDE0017 / IDE0400
routing from PR 7). This prevents the two analyzers from double-reporting
on mixed scenarios under the mixed object/collection initializer feature
(csharplang#10185).

Per-language concretes (C# + VB) gain a new `TAssignmentStatementSyntax`
generic slot threaded through the walk → diagnostic analyzer → fix provider
chain so the unified member-init detection can use the language's
assignment-statement-shaped type.

The pre-existing `AbstractUseNamedMemberInitializerAnalyzer` walk and
`AbstractUseObjectInitializerDiagnosticAnalyzer` diagnostic analyzer are
intentionally left in place by this pass — they continue to handle pure-
member-init scenarios on types without an accessible `Add` (where IDE0028's
strict `ShouldAnalyze` rules out the walk entirely). A follow-on pass can
delete those once IDE0028's `ShouldAnalyze` is loosened in lock-step with
tightening the collection-expression synthesis path (`CanUseCollectionExpression`)
to skip problematic existing initializers.

942 init-suite tests + 20620-test full Features suite both green; zero
behavior changes user-visibly. The cross-analyzer double-report bug that
Pass 3 was specifically aimed at preventing (the walk now produces member
matches that IDE0028 would have reported on without the new cede) is closed
by the member-init bail-out in `AnalyzeNode`.

Stacked:
* dotnet#83750 mixed-init-syntax-facts (parser tests)
* dotnet#83751 mixed-init-bind (binding + lowering + flow + emit + ET)
* dotnet#83752 mixed-init-ide-foundation (formatter + speculation + UseUtf8)
* dotnet#83753 mixed-init-semmodel (SemanticModel + find-refs + IOperation)
* dotnet#83754 mixed-init-ide0017-extension (IDE0017 Add-fold extension)
* dotnet#83755 mixed-init-ide-polish (AddImport + CanReplaceWithLValue carve-outs)
* dotnet#83757 mixed-init-unify-analyzers (IDE0017+IDE0028 routing + IDE0400)
* dotnet#83758 mixed-init-unify-pass1-match-shape (Pass 1: shared match shape)
* dotnet#83760 mixed-init-unify-pass2-fix-providers (Pass 2: merged fix providers)
* >>> this PR — Pass 3a: merged walk (member-init produced by unified walk) <<<
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area-Infrastructure Community The pull request was submitted by a contributor who is not a Microsoft employee. VSCode

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant