Skip to content

Keep instance-dependent assignments in constructors - #3980

Open
sailro wants to merge 2 commits into
icsharpcode:masterfrom
sailro:fix-primary-constructor-field-dependency
Open

Keep instance-dependent assignments in constructors#3980
sailro wants to merge 2 commits into
icsharpcode:masterfrom
sailro:fix-primary-constructor-field-dependency

Conversation

@sailro

@sailro sailro commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

tl;dr: Do not introduce a primary constructor when a moved initializer depends on the instance.

Fixes #3979

Problem

TransformFieldAndConstructorInitializers can promote a constructor even when a later assignment reads an instance member initialized by an earlier assignment.

Moving both assignments to field initializers produces CS0236.

Solution

Reject primary-constructor conversion when a candidate initializer references this or a non-generated instance member through this.

  • At least one test covering the code changed

sailro and others added 2 commits August 12, 2026 09:24
A moved field initializer cannot read another instance member. Reject primary-constructor conversion for that case and preserve the original constructor.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 0dd407b6-9410-48df-add5-761ca4a8dec0
The guard added here reads the target of a member access to decide whether an
assignment may move into a field initializer, and it recognises the current
instance as a ThisResolveResult. Only one of the two spellings produces that.
An unqualified `A` is resolved through CSharpResolver.LookInCurrentType, which
synthesizes the target as a this-reference; an explicit `this.A` is built by
ExpressionBuilder, whose TranslateTarget hands back whatever ConvertVariable
produced - and `this` is a parameter like any other there, so the target is an
ILVariableResolveResult. The guard saw the first and missed the second.

Which spelling appears is decided by RequiresQualifier, for reasons unrelated
to the question being asked: a constructor parameter that shadows the field
forces the qualifier, and AlwaysQualifyMemberReferences forces it everywhere.
So the transform hoisted `b = this.value + 1` into a field initializer, where
naming the instance is CS0027 and the output does not compile.

TranslateTarget already builds a ThisResolveResult for `base`, one branch
above. Doing the same for `this` leaves the guard untouched and makes it see
both spellings, and spares every future consumer the same trap. The type is
carried over from the previous resolve result, so nothing downstream observes
a different one - the this/base keyword links read exactly this node.

Fixes icsharpcode#3984.

Assisted-by: Claude:claude-opus-5:Claude Code
@siegfriedpammer
siegfriedpammer force-pushed the fix-primary-constructor-field-dependency branch from c06a8ab to 13c6cc6 Compare August 12, 2026 07:24
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.

Primary-constructor conversion creates an invalid field initializer (CS0236)

2 participants