Skip to content

init Properties on Interfaces With DIMs Causes Diagnostics #373

@JasonBock

Description

@JasonBock

To Reproduce

Run this code in a test:

var code =
	"""
	using Rocks;
	
	[assembly: Rock(typeof(IBufferedChannel), BuildType.Create | BuildType.Make)]
	
	public interface IBufferedChannel
	{
		int MaxBufferSize { get; init; }
		void Consume(int count) { }
	}
	""";

Expected behavior
Rocks generates code with no issues.

Actual behavior
A compiler diagnostic is created:

error CS8852: Init-only property or indexer 'IBufferedChannel.MaxBufferSize' can only be assigned in an object initializer, or on 'this' or 'base' in an instance constructor or an 'init' accessor.

Additional context
This is hard to describe why this is a problem. The issue is in the generate shim:

private sealed class ShimIBufferedChannel
	: global::IBufferedChannel
{
	private readonly Mock mock;
						
	public ShimIBufferedChannel(Mock @mock) =>
		this.mock = @mock;
						
	public int MaxBufferSize
	{
		get => ((global::IBufferedChannel)this.mock).MaxBufferSize!;
		init => ((global::IBufferedChannel)this.mock).MaxBufferSize = value!;
	}
}

For some reason, calling MaxBufferSize on the "parent" mock with the cast in the init isn't allowed.

One idea to fix this is to simply not do anything in the init:

private sealed class ShimIBufferedChannel
	: global::IBufferedChannel
{
	private readonly Mock mock;
						
	public ShimIBufferedChannel(Mock @mock) =>
		this.mock = @mock;
						
	public int MaxBufferSize
	{
		get => ((global::IBufferedChannel)this.mock).MaxBufferSize!;
		init;
	}
}

The init (and, for that matter, the get) will never be called from the mock instance itself. Therefore, it doesn't matter what they do. The property has to be implemented, but what it does is irrelevant.

If the property itself was a DIM, the same issue occurs within the mock when it tries to call the shim's property implementation from its' init implementation. Note that the get implementation is fine. This is really odd, because the assignment is within an init accessor, which is what the diagnostic is complaining about. But we want to call the DIM's implementation.

For reference: dotnet/roslyn#50053

This was found in DotNext.IO, DotNext.Buffers.IBufferedReader.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions