Diagnostics
All diagnostics are in the IfItQuacks category and reported as errors, except the warning IFITQUACKS012.
IFITQUACKS001
Argument does not structurally satisfy interface
The argument's type is missing a member of the interface, or a member has an incompatible type (see Matching an interface), no public getter/setter or is a readonly field where the interface declares a setter. It is also reported for anonymous types passed to generic [DuckTyped] methods, and for anonymous types with a property whose type contains another anonymous type as a type argument or array element. For generic [DuckTyped] methods this is also reported when the type arguments can't be inferred from the argument. The compiler reports CS0411 in addition, because no generic fallback overload exists for these methods.
The message lists every member that doesn't fit, and points out a member that exists but is not public, is static or differs only in casing:
public class Rock { public void do() { } }
Ops.Foo(new Rock()); // error IFITQUACKS001: Type 'Rock' does not structurally satisfy 'IDoable': missing method 'void IDoable.Do()' - did you mean 'do'?
IFITQUACKS002
Duck-typed method's containing type must be partial
The generator adds a fallback overload to the containing type, so it (and every enclosing type) has to be partial.
IFITQUACKS003
Duck-typed method needs an interface parameter
None of the parameters of a [DuckTyped] method is an interface passed by value.
IFITQUACKS004
Unsupported [DuckTyped] method signature
Reported when
- the containing type (or one of its enclosing types) is generic,
- the method is overloaded by another
[DuckTyped]method of the same name in the same type, - the method is overloaded by a generic method with the signature of its generated fallback overload, e.g.
Greet<T>(T value)next toGreet(INamed n), - a base type has an accessible method of the same name, which the generated fallback would hide (overload resolution ignores a base type's methods once one of the derived type applies, so
OverloadResolutionPriorityAttributecan't help); this includes the methods ofobject, e.g. a[DuckTyped]method namedEquals, - the method is overloaded by a regular method of the same name, but the project uses a language version below C# 13, which is needed for
OverloadResolutionPriorityAttributeto keep the generated fallback from taking that overload's calls (on .NET 8, set<LangVersion>13</LangVersion>), - the containing type is an interface or a
file-local type, - a type parameter of a generic method is not used by any interface parameter (it can't be inferred), or
- the method mixes interface parameters with duck-typed constraints, or not all of its type parameters have a single interface constraint used by a parameter.
IFITQUACKS005
Unsupported interface member
The interface contains a member an adapter can't implement: a generic method (U Map<U>()), or a static abstract member outside a duck-typed constraint. For a duck-typed constraint it is reported for an unsupported operator and for instance members whose signature uses the self type. It is reported on every argument that would need an adapter; types implementing the interface are fine.
public interface IMapper { U Map<U>(); }
Ops.Run(new Mapper()); // error IFITQUACKS005
IFITQUACKS006
Unsupported struct argument
The argument is a mutable struct or a ref struct. A mutable struct would be copied into the adapter, so mutations made through the interface would be lost silently. A ref struct can't be converted to an interface at all. Use a class, a readonly struct, or let the struct implement the interface. See Known limitations.
public struct Counter { public int Count { get; private set; } public void Increment() => Count++; }
Ops.Bump(new Counter()); // error IFITQUACKS006
For ref structs the compiler additionally reports CS9244, because the generated fallback overload can't accept a ref struct.
IFITQUACKS007
Duck conversion target must be an interface
Duck.As, Duck.Stub and Duck.Merge only convert to interfaces. This is also reported for type parameters, because the target can't be verified at compile time. Duck.To is the one that takes a concrete type.
public class Duckling { }
var duckling = Duck.As<Duckling>(new A()); // error IFITQUACKS007
IFITQUACKS008
Unsupported duck-typed call
The call itself can't be redirected, whatever its arguments are. So far this is only a base call: it invokes the method non-virtually, which neither an interceptor nor the generated fallback overload can reproduce - both would end up in the overriding method.
public override string Greet(INamed named) => "derived";
public string ViaBase() => base.Greet(new Person()); // error IFITQUACKS008
Pass a value already typed as the interface, or call the method without base.
IFITQUACKS009
Unsupported Duck.To target
Duck.To<TTarget> can't build the target from the source: the target is abstract, an interface (use Duck.As instead), has no accessible constructor that the source can fill, or has a member the source has no counterpart for.
public record PersonDto(string Name, int Age);
public class OnlyName { public string Name { get; set; } = ""; }
Duck.To<PersonDto>(new OnlyName()); // error IFITQUACKS009: no member of the source fills 'Age'
IFITQUACKS010
Mapped shape target must be a partial interface
[DuckShape<T>] fills a second declaration of the interface, so the interface - and every type around it - has to be partial. A generic or file-local containing type is reported as well.
[DuckShape<Customer>]
public interface ICustomerView; // error IFITQUACKS010: it is not declared 'partial'
IFITQUACKS011
Unsupported [DuckShape<>] usage
The mapping can't be applied: Pick and Omit are both set, a name in Pick or Omit is not a public instance member of the source, the source is not a named class, struct, record or interface, the mapping derives no member at all, or a derived member's type is less accessible than the interface.
[DuckShape<Customer>(Omit = ["Nope"])]
public partial interface ICustomerView; // error IFITQUACKS011: 'Nope' is not a public instance member of the source
See Mapped shapes.
IFITQUACKS012
Duck-typed parameter is cast to a concrete type (warning)
An argument that only matches the interface structurally is wrapped in a generated adapter, and that adapter is what your method receives. With a duck-typed constraint, T is the adapter type itself. A cast, as or type pattern on the parameter for a concrete type, typically the caller's own type, therefore fails for these calls. It succeeds for arguments that implement the interface, so the method behaves differently depending on the caller.
[DuckTyped]
public static string Describe(INamed named) =>
named is Person p ? p.Nickname : named.Name; // warning IFITQUACKS012: Whenever the argument doesn't implement 'INamed' itself,
// 'named' receives a generated adapter instead of the caller's instance, so this
// type test for 'Person' never succeeds for such calls; use 'Duck.Unwrap(named)'
// to get the original instance
Conversion operators can't bridge this: C# doesn't allow user-defined conversions from an interface, and inside a generic method the compiler doesn't know the adapter type. Test the original instance instead:
Duck.Unwrap(named) is Person p ? p.Nickname : named.Name;
Casts and type tests for interfaces and type parameters are not reported, because the adapter may implement those.