| Severity | Warning |
| Reported by | Metalama.Framework (Roslyn analyzer) |
| Message | '<Type>' must be written in an immutable style because it is an aspect, a fabric, a validator, or another type bound by the [ImmutableType] contract, but the automatic property '<Property>' has a setter that is not private, so not every assignment to it can be seen. Replace 'set' with 'init', make the setter private, or, for a positional record struct, declare it as a 'readonly record struct'. |
Cause
An aspect is instantiated once and reused for every declaration it applies to, and at design time it is reused across compilations. State that you keep in an aspect instance therefore leaks from one target to the next, in an order that you don't control. That's why Metalama requires aspects, fabrics, and validators to be written in an immutable style. The requirement comes from the [ImmutableType] attribute, which is applied to IAspect and Fabric, and which every type that derives from or implements a marked type inherits.
For such a type, every automatic property must have no setter, an init accessor, or a private setter that is assigned only in a constructor or in an init accessor. A property whose setter is public, internal, or protected can be assigned by code that the analyzer can't see, so the declaration itself is reported.
Metalama reports LAMA0881 on the declaration of an instance automatic property whose set accessor is neither private nor init. In the message, <Type> is the aspect, fabric, validator, or other type subject to the contract, and <Property> is the name of the property.
A positional record struct, as in record struct Range( int Start, int End ), is a special case. The compiler generates a settable property for each parameter of its primary constructor, so the type violates the contract although it looks like a record class. Metalama then reports the warning on the parameter, and the fix is to declare the type as a readonly record struct.
Fields are reported with LAMA0880.
This diagnostic is reported by a Roslyn analyzer, which snapshot tests don't run, so it can't be shown in an example.
Resolution
Choose one of the following options:
- Replace
setwithinit, and assign the property in an object initializer or in the constructor. This is the preferred option for a property that callers must be able to configure. - Remove the setter, and assign the property from the constructor.
- Make the setter private, and make sure that the property is assigned only in a constructor.
- For a positional
record struct, add thereadonlymodifier to make it areadonly record struct. - Move the state out of the aspect. Keep per-target state in the objects that your template or
BuildAspectmethod creates, and not in a property of the aspect.