Skip to content

Handle unresolved value-type call targets - #4053

Merged
dgrunwald merged 2 commits into
icsharpcode:masterfrom
sonyps5201314:split/unresolved-value-type-call-targets
Sep 3, 2026
Merged

Handle unresolved value-type call targets#4053
dgrunwald merged 2 commits into
icsharpcode:masterfrom
sonyps5201314:split/unresolved-value-type-call-targets

Conversation

@sonyps5201314

Copy link
Copy Markdown
Contributor

Related to #4049.

Problem

When an instance method belongs to a value type from a missing assembly, its declaring type has unknown stack semantics. ILReader inserts an invalid Ref -> Unknown conversion around the managed-reference receiver.

ExpressionBuilder.TranslateTarget then treats the conversion as a value receiver and emits invalid C# such as:

((MyEnumerator)(ref value)).MoveNext();
((IDisposable)(*(MyEnumerator*)(&value))).Dispose();

Solution

For the narrowly defined Invalid, Ref -> Unknown conversion on an unresolved call target:

  • use the existing UnwrapConv helper to recover the original managed-reference receiver;
  • infer managed-reference target semantics from the IL argument when the unresolved declaring type cannot provide them;
  • treat same-named unresolved by-reference element types as the same receiver type.

This PR contains only this call-target fix and its Issue3729 regression case. It does not include constructor-initializer, primary-constructor, collection-expression, resolver, project export, or backing-field changes from #4049.

Test

The ILPretty case was added first. Before the implementation it failed with an unsafe method, a (MyEnumerator)(ref value) cast, and a pointer dereference for the constrained IDisposable.Dispose call.

After the implementation:

  • ILPrettyTestRunner.Issue3729: passed.
  • Complete ILPrettyTestRunner fixture: 63 total, 62 passed, 1 pre-existing ignored, 0 failed.
  • Full Release|Any CPU solution build: 16 succeeded, 0 failed.
  • BuildTools/format.ps1: clean.
  • At least one test covering the code changed

IType expectedTargetType = constrainedTo ?? memberDeclaringType;
if (!(translatedTarget.Type is ByReferenceType brt
&& (NormalizeTypeVisitor.TypeErasure.EquivalentTypes(brt.ElementType, expectedTargetType)
|| (expectedTargetType.Kind == TypeKind.Unknown && brt.ElementType.ReflectionName == expectedTargetType.ReflectionName))))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The ReflectionName string comparison is now unnecessary with the change on #4070.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed. I removed the ReflectionName fallback and now rely solely on TypeErasure.EquivalentTypes after #4070. The single commit was force-pushed as d730ec0a2.

I also applied it without conflicts on current master (05d274ff7) and verified:

  • BuildTools/format.ps1: clean;
  • full Release|Any CPU solution build: 16 succeeded, 0 failed;
  • complete ILPrettyTestRunner: 64 total, 63 passed, 1 pre-existing ignored, 0 failed.

A missing value-type definition makes ILReader insert a Ref-to-Unknown conversion before instance calls. Preserve the managed-reference receiver so C# output does not fall back to invalid ref casts or unsafe pointers.

Assisted-by: Codex:gpt-5.6-sol:Codex
@sonyps5201314
sonyps5201314 force-pushed the split/unresolved-value-type-call-targets branch from e28fabc to d730ec0 Compare August 30, 2026 11:07
@dgrunwald
dgrunwald merged commit 56f45ae into icsharpcode:master Sep 3, 2026
9 checks passed
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.

2 participants