Repository navigation
CMTK0006 reports an explicit interface property that every constructor assigns through ((IHasId)this).Id = id #231
Description
Found by a pre-release sweep of release/2.3.1 @ dbdd44e plus #216, #217 and #218 as landed (lens: long-method, analyzers); reproduced in a scratch analyzer test.
Package
CodoMetis.TypeKit.Analyzers
Version
2.3.0 (measured on v2.3.0), and 2.3.1 as above
How does it fail?
A false positive: CMTK0006 is reported on code that does what its advice says. It is a Warning, so a warnings-as-errors build fails.
Problem
UnassignedNoDefaultMemberAnalyzer.Assignments.Collect (src/CodoMetis.TypeKit.Analyzers/UnassignedNoDefaultMemberAnalyzer.cs:329-345) records a property assignment only as IPropertyReferenceOperation { Instance: IInstanceReferenceOperation }, and adds property.Property. A class can reach its own explicit interface implementation only through a conversion, ((IHasId)this).Id = id. That assignment's target is the interface's property, and its Instance is an IConversionOperation over this. So the pattern doesn't match. Even if it did, the symbol would be IHasId.Id, not the class's implementation that is the candidate, and Members.Contains would miss it.
The member is a candidate on purpose: UnassignedNoDefaultMemberAnalyzerTests.cs:141 (ExplicitImplementation) holds that one that nothing sets is reported. The README's CMTK0006 advice and the message both say "assign it in every constructor".
Scenario
public interface IHasId { Plain Id { get; set; } }
public sealed class Order : IHasId
{
public Order(Plain id) => ((IHasId)this).Id = id;
Plain IHasId.Id { get; set; }
}Measured on v2.3.0 and on 2.3.1: warning CMTK0006: 'IHasId.Id' starts as a default 'Plain', which passed no factory: it has no initializer, is not 'required', and the constructor 'Order(Plain)' does not assign it. Initialize it, or assign it in every constructor. Expected: no diagnostic. A local first, IHasId self = this; self.Id = id;, reports the same. That form isn't an assignment to the instance, so whether it should count is the fix's decision.
Change
In Collect, strip conversions from Instance down to an IInstanceReferenceOperation. When the property is an interface member, map it to the analysed type's implementation (type.FindImplementationForInterfaceMember) before adding it. The same goes for a ref/out argument and a tuple element, which go through Collect as well.
Test
Input class: the forms that assign a member in a constructor (plain, this., a cast of this to each interface the member implements, deconstruction, out/ref). Rows in UnassignedNoDefaultMemberAnalyzerTests: an explicit implementation assigned through the cast is silent. Two constructors where only one assigns through the cast report CMTK0006 naming the other, so the mapping doesn't turn into "assigns everything". Reverted per verify-the-guard.
Environment
.NET SDK 10.0.4xx; any consumer.
Activity
CaffeinatedCoder commentedon Oct 31, 2026
Fixed in #243. Decision on the local alias:
IHasId self = this; self.Id = id;stays reported, since the rule doesn't follow locals; the README's CMTK0006 section now says so. The cast fix covers more than the explicit case: an implicit implementation reached through the cast, the second of two interfaces, a generic interface, a cast to the class itself (a field) andoutthrough it were all reported and are silent now. A sibling found running the input class (Found-By: VA, its own commit): an object initializer orwithof another instance inside a constructor counted as assigning this one, so CMTK0006 was silent there; only the containing instance counts now.CaffeinatedCoder commentedon Oct 31, 2026
Fixed by #243, merged into
release/2.3.1(88ae0fe); ships in 2.3.1.