Skip to main content

Diagnostics

Awaiten checks your wiring while it builds. A mistake that other containers would surface at startup, or on the first resolve in production, is a compile error here. The barista never reaches for a cup that is not there.

Every diagnostic has an AWT### id. Errors stop the build. Warnings point at something that is legal but probably not what you meant. This page is the full catalogue, one entry per id with a concrete example that triggers it. The feature pages link here for the codes they mention.

The examples reuse a few coffee-shop types (Grinder, EspressoMachine, Order, Cup, IMilk, IBrewer, and so on). Only the detail that trips the diagnostic is spelled out in each snippet.

Graph correctness

These check that the object graph can actually be built.

AWT101

Error

A required dependency has no registration.

public sealed class Cup(Grinder grinder);

[Container]
[Transient<Cup>] // Grinder is never registered
public static partial class CoffeeShop;

AWT102

Error

A dependency cycle exists in the object graph.

public sealed class Till(Printer printer);
public sealed class Printer(Till till); // Till needs Printer needs Till

[Container]
[Transient<Till>]
[Transient<Printer>]
public static partial class CoffeeShop;

AWT103

Error

An implementation type is abstract or an interface, and no Factory or Instance member produces it.

public interface IBrewer;

[Container]
[Singleton<IBrewer>] // IBrewer is an interface, not something to construct
public static partial class CoffeeShop;

A Factory (or, on a singleton, Instance) member lifts the requirement: the container constructs nothing then, so the type argument only names the service the produced instance is resolved as. That is the shape for a library factory whose concrete type cannot be named: an internal implementation behind a public interface, such as the logger a SerilogLoggerFactory creates.

public interface IBrewer;

[Container]
[Singleton<IBrewer>(Factory = nameof(CreateBrewer))] // the factory produces it; IBrewer names the service
public static partial class CoffeeShop
{
private static IBrewer CreateBrewer() => BrewerLibrary.Create(); // returns an internal implementation
}

AWT104

Error

An implementation type has no accessible constructor.

public sealed class Grinder
{
private Grinder() { } // only a private constructor
}

[Container]
[Singleton<Grinder>]
public static partial class CoffeeShop;

AWT105

Error

A singleton captures a shorter-lived scoped dependency.

public sealed class EspressoMachine(Order order); // singleton needs a scoped Order

[Container]
[Singleton<EspressoMachine>]
[Scoped<Order>]
public static partial class CoffeeShop;

AWT107

Error

An implementation is registered with conflicting lifetimes.

[Container]
[Singleton<Grinder>]
[Transient<Grinder>] // same implementation, two lifetimes
public static partial class CoffeeShop;

AWT111

Error

An implementation is registered with conflicting production strategies.

[Container]
[Singleton<Grinder>(Factory = nameof(MakeGrinder))]
[Singleton<Grinder>] // one via factory, one via constructor
public static partial class CoffeeShop
{
private static Grinder MakeGrinder() => new();
}

AWT116

Error

A [Container] class is not declared static.

[Container]
public partial class CoffeeShop; // must be static partial

AWT117

Error

Two registrations share the same service type and key.

[Container]
[Singleton<OatMilk, IMilk>(Key = "Oat")]
[Singleton<SoyMilk, IMilk>(Key = "Oat")] // same service and key
public static partial class CoffeeShop;

AWT148

Warning

Two overridable default registrations provide the same service ambiguously.

[Module]
[Singleton<RealTimeSystem, ITimeSystem>(Fallback = Fallback.Warn)]
public static class TimeModuleA;

[Module]
[Singleton<MockTimeSystem, ITimeSystem>(Fallback = Fallback.Warn)]
public static class TimeModuleB;

[Container]
[Import(typeof(TimeModuleA))]
[Import(typeof(TimeModuleB))] // two defaults for ITimeSystem, neither wins
public static partial class CoffeeShop;

Async safety

These keep async-initialized services from being reached before they are ready.

AWT106

Warning

A synchronous factory's body provably produces an IAsyncInitializable concrete type its declared return type hides.

public sealed class AsyncBrewer : IBrewer, IAsyncInitializable
{
public Task InitializeAsync(CancellationToken ct) => Task.CompletedTask;
}

[Container]
[Singleton<IBrewer>(Factory = nameof(MakeBrewer))]
public static partial class CoffeeShop
{
// declared IBrewer, but the concrete type is async-initialized
private static IBrewer MakeBrewer() => new AsyncBrewer();
}

AWT119

Error

A synchronous Func/Lazy/Owned relationship targets an async-initialized service.

public sealed class EspressoMachine : IAsyncInitializable
{
public Task InitializeAsync(CancellationToken ct) => Task.CompletedTask;
}
public sealed class Barista(Func<EspressoMachine> machine); // sync Func over an async service

[Container]
[Singleton<EspressoMachine>]
[Singleton<Barista>]
public static partial class CoffeeShop;

AWT120

Error

A synchronous Func/Lazy/Owned relationship reaches an async-tainted service transitively.

public sealed class EspressoMachine : IAsyncInitializable
{
public Task InitializeAsync(CancellationToken ct) => Task.CompletedTask;
}
public sealed class Grinder(EspressoMachine machine); // async-tainted through the machine
public sealed class Barista(Func<Grinder> grinder); // sync Func reaches it transitively

[Container]
[Singleton<EspressoMachine>]
[Transient<Grinder>]
[Singleton<Barista>]
public static partial class CoffeeShop;

AWT122

Error

A collection dependency has an async-tainted member but is materialized synchronously.

public sealed class Latte : IDrink, IAsyncInitializable
{
public Task InitializeAsync(CancellationToken ct) => Task.CompletedTask;
}
public sealed class Menu(IEnumerable<IDrink> drinks); // sync collection with an async member

[Container]
[Transient<Latte, IDrink>]
[Singleton<Menu>]
public static partial class CoffeeShop;

AWT156

Warning

A generated Root/Scope is disposed synchronously although its container owns a service that implements IAsyncDisposable but not IDisposable.

public sealed class Boiler : IAsyncDisposable
{
public ValueTask DisposeAsync() => default;
}

[Container]
[Singleton<Boiler>]
public static partial class CoffeeShop;

using (var shop = new CoffeeShop.Root()) { } // sync dispose cannot tear down Boiler; use await using

AWT161

Error

An eager singleton is async-initialized and cannot be constructed synchronously at container build time.

public sealed class EspressoMachine : IAsyncInitializable
{
public Task InitializeAsync(CancellationToken ct) => Task.CompletedTask;
}

[Container]
[Singleton<EspressoMachine>(Eager = true)] // async service cannot be built eagerly at construction
public static partial class CoffeeShop;

Factories and instances

AWT108

Error

A Factory registration names a member that is not a usable factory method.

[Container]
[Singleton<Grinder>(Factory = nameof(Count))] // Count is a field, not a factory method
public static partial class CoffeeShop
{
private static int Count = 3;
}

AWT109

Error

An Instance registration names a member that is not a usable instance member.

[Container]
[Singleton<Grinder>(Instance = nameof(MakeGrinder))] // MakeGrinder is a method, not an instance member
public static partial class CoffeeShop
{
private static Grinder MakeGrinder() => new();
}

AWT110

Error

A registration sets both Factory and Instance.

[Container]
[Singleton<Grinder>(Factory = nameof(MakeGrinder), Instance = nameof(Shared))]
public static partial class CoffeeShop
{
private static Grinder MakeGrinder() => new();
private static Grinder Shared { get; } = new();
}

AWT112

Error

A Factory registration names an overloaded method.

[Container]
[Singleton<Grinder>(Factory = nameof(MakeGrinder))] // MakeGrinder is overloaded
public static partial class CoffeeShop
{
private static Grinder MakeGrinder() => new();
private static Grinder MakeGrinder(int burrs) => new();
}

AWT162

Error

A [RequestingType] factory parameter is not of type System.Type.

[Container]
[Transient<ILogger>(Factory = nameof(CreateLogger))]
public static partial class CoffeeShop
{
private static Logger CreateLogger([RequestingType] string consumer) => new(consumer); // must be Type
}

AWT163

Error

A factory has both a [RequestingType] parameter and an [Arg] runtime-argument parameter.

[Container]
[Transient<ILogger>(Factory = nameof(CreateLogger))]
public static partial class CoffeeShop
{
private static Logger CreateLogger([RequestingType] Type consumer, [Arg] string name)
=> new(consumer, name);
}

AWT186

Error

An Owned<T> relationship (or its Func/Task forms) targets a service produced by a requesting-type factory. Such a factory is called per consumer and decides its own disposal, so it has no owner scope to build the owned target into. Consume the service directly, or through Func<T> / Lazy<T>.

public sealed class Barista(Owned<ILogger> logger); // Owned<T> over a requesting-type factory

[Container]
[Transient<ILogger>(Factory = nameof(CreateLogger))]
[Transient<Barista>]
public static partial class CoffeeShop
{
private static Logger CreateLogger([RequestingType] Type? consumer) => new(consumer?.Name ?? "<root>");
}

Lifecycle hooks

AWT164

Error

An OnActivated/OnRelease registration names a member that is not a usable lifecycle hook.

[Container]
[Singleton<EspressoMachine>(OnActivated = nameof(Ready))] // Ready is a field, not a hook method
public static partial class CoffeeShop
{
private static bool Ready = false;
}

AWT165

Error

An OnActivated/OnRelease lifecycle hook is set on a pre-built Instance registration, which the container does not own.

[Container]
[Singleton<MenuBoard>(Instance = nameof(Menu), OnRelease = nameof(Archive))]
public static partial class CoffeeShop
{
private static MenuBoard Menu { get; } = new();
private static void Archive(object board) { } // the container does not own an Instance
}

AWT166

Error

An implementation is registered with conflicting OnActivated/OnRelease/Eager directives that coalescing would silently drop.

[Container]
[Singleton<EspressoMachine, IWarmable>(OnActivated = nameof(Calibrate))]
[Singleton<EspressoMachine, IMachine>(OnActivated = nameof(Preheat))] // two hooks for one implementation
public static partial class CoffeeShop
{
private static void Calibrate(object m) { }
private static void Preheat(object m) { }
}

AWT189

Error

A lifecycle hook parameter (after the instance) is marked [Arg], but a hook resolves its parameters from the graph.

[Container]
[Singleton<EspressoMachine>(OnActivated = nameof(Calibrate))]
public static partial class CoffeeShop
{
private static void Calibrate(EspressoMachine machine, [Arg] int count) { } // no Func<…> call site supplies an [Arg]
}

AWT190

Error

A lifecycle hook (OnActivated / OnRelease) names an overloaded method, so there is no way to choose which one to call.

A hook is looked up by simple name on its owner (the container, or a module whose [Scan] names it), so two accepting overloads leave the choice (and the graph dependencies the extra parameters resolve) order-dependent. Give the hook a unique name, exactly as a factory method must be unambiguous.

[Container]
[Singleton<EspressoMachine>(OnActivated = nameof(Calibrate))]
public static partial class CoffeeShop
{
private static void Calibrate(EspressoMachine machine) { }
private static void Calibrate(EspressoMachine machine, Settings settings) { } // which one runs?
}

AWT191

Error

An OnRelease hook parameter is a Func/Lazy relationship, which would defer resolution past the owner's teardown.

A release dependency is captured at construction, but a Func<T> or Lazy<T> captures only a resolver delegate. The hook runs while its owner is being disposed, so invoking the delegate there always throws. Take the dependency directly instead: it is resolved at construction and, released in reverse creation order, still alive when the hook uses it. An OnActivated hook may take Func/Lazy parameters freely, since it runs while the owner is alive.

[Container]
[Singleton<BufferPool>]
[Transient<Buffer>(OnRelease = nameof(ReturnToPool))]
public static partial class CoffeeShop
{
private static void ReturnToPool(Buffer buffer, Func<BufferPool> pool) { } // pool() would throw during teardown; take BufferPool directly
}

Runtime arguments

AWT113

Error

A Func<TArg...,T> or Func<TArg...,Task<T>> relationship's runtime arguments do not match the service's [Arg] parameters.

public sealed class Cup([Arg] string size);

[Container]
[Transient<Cup>]
public static partial class CoffeeShop;

var make = shop.Resolve<Func<int, Cup>>(); // the [Arg] is string, not int

AWT114

Error

A service with [Arg] parameters is registered with a non-Transient lifetime.

public sealed class Cup([Arg] string size);

[Container]
[Singleton<Cup>] // [Arg] services must be transient
public static partial class CoffeeShop;

AWT115

Error

A service with [Arg] parameters is required as a plain, Lazy<T>, or Task<T> dependency instead of a Func<TArg...,T>.

public sealed class Cup([Arg] string size);
public sealed class Barista(Cup cup); // must ask for Func<string, Cup>

[Container]
[Transient<Cup>]
[Singleton<Barista>]
public static partial class CoffeeShop;

AWT137

Error

An injected property is marked [Arg].

public sealed class Barista
{
[Inject, Arg] public string? Name { get; set; } // [Arg] is for constructor and factory parameters
}

[Container]
[Transient<Barista>]
public static partial class CoffeeShop;

Ownership and lifetime safety

AWT118

Error

A root-owned instance holds a Func or Func<…,Task<T>> over a disposable build-on-demand service. Under strict lifetime safety (the default) this is a non-suppressible error; under LifetimeSafety.Loose it relaxes to a suppressible warning.

public sealed class BrewSession : IDisposable { public void Dispose() { } }
public sealed class Counter(Func<BrewSession> sessions); // each call strands a disposable on the root

[Container] // strict lifetime safety (the default)
[Transient<BrewSession>]
[Singleton<Counter>]
public static partial class CoffeeShop;

AWT121

Error

An Owned<T> disposal handle is requested through a Lazy<Owned<T>> or Lazy<Task<Owned<T>>> relationship.

public sealed class BrewSession : IDisposable { public void Dispose() { } }
public sealed class Counter(Lazy<Owned<BrewSession>> sessions); // Owned cannot hide behind Lazy

[Container]
[Transient<BrewSession>]
[Singleton<Counter>]
public static partial class CoffeeShop;

AWT192

Error

SuppressDisposal is set on a pre-built Instance, which the container does not own or dispose, so it has no effect. Remove it, or register the type for construction (by constructor or Factory) instead of as an Instance.

public sealed class Boiler : IDisposable { public void Dispose() { } }

[Container]
[Singleton<Boiler>(Instance = nameof(Shared), SuppressDisposal = true)] // an Instance is never disposed anyway
public static partial class CoffeeShop
{
private static Boiler Shared { get; } = new();
}

Decorators and composites

AWT123

Error

A [Decorate] names a service with no registration to decorate. Also raised when an open generic [Decorate(typeof(D<>), typeof(IService<>))] matches no closing of its service in the graph.

public sealed class LoggingTerminal(IPaymentTerminal inner) : IPaymentTerminal;

[Container]
[Decorate<LoggingTerminal, IPaymentTerminal>] // nothing registers IPaymentTerminal
public static partial class CoffeeShop;

AWT124

Error

A decorator has no single constructor parameter assignable to the decorated service type.

public sealed class LoggingTerminal : IPaymentTerminal; // no IPaymentTerminal inner parameter

[Container]
[Singleton<CardTerminal, IPaymentTerminal>]
[Decorate<LoggingTerminal, IPaymentTerminal>]
public static partial class CoffeeShop;

AWT130

Error

A [Composite] implementation has no collection parameter of the composed service to fan out to.

public sealed class CompositeReceiptChannel : IReceiptChannel; // no IEnumerable<IReceiptChannel> parameter

[Container]
[Transient<PrinterChannel, IReceiptChannel>]
[Composite<CompositeReceiptChannel, IReceiptChannel>]
public static partial class CoffeeShop;

AWT131

Warning

A [Composite] type is also registered as an ordinary member of the service it composes.

public sealed class CompositeReceiptChannel(IEnumerable<IReceiptChannel> channels) : IReceiptChannel;

[Container]
[Transient<CompositeReceiptChannel, IReceiptChannel>] // registered as a normal member too
[Composite<CompositeReceiptChannel, IReceiptChannel>]
public static partial class CoffeeShop;

AWT132

Error

More than one [Composite] names the same service.

[Container]
[Composite<CompositeReceiptChannel, IReceiptChannel>]
[Composite<BackupReceiptChannel, IReceiptChannel>] // two composites for one service
public static partial class CoffeeShop;

AWT133

Error

A [Composite]'s collection parameter is of a base type of the composed service, not the composed service itself.

// IReceiptChannel : IChannel
public sealed class CompositeReceiptChannel(IEnumerable<IChannel> channels) : IReceiptChannel; // IChannel is a base type

[Container]
[Transient<PrinterChannel, IReceiptChannel>]
[Composite<CompositeReceiptChannel, IReceiptChannel>]
public static partial class CoffeeShop;

Open generics

AWT125

Error

An open generic registration's implementation and service have different arity. The open [Decorate]/[Composite] typeof forms are held to the same rule (decorator/composite arity must match the service).

[Container]
[Transient(typeof(Repository<>), typeof(IRepository<,>))] // one type parameter vs two
public static partial class CoffeeShop;

AWT126

Error

A required closed type violates the open generic implementation's type-parameter constraints, so the dependency cannot be satisfied. (For the open [Decorate]/[Composite] case, where the base service still resolves, see the warning AWT171.)

public sealed class Repository<T> : IRepository<T> where T : class;
public sealed class Ledger(IRepository<int> repo); // int is not a class

[Container]
[Transient(typeof(Repository<>), typeof(IRepository<>))]
[Singleton<Ledger>]
public static partial class CoffeeShop;

AWT171

Warning

The decorator/composite counterpart to AWT126: a closing of an open generic [Decorate]/[Composite] cannot be constructed because its type arguments violate the decorator's or composite's type-parameter constraints. A warning, not an error, because the base service still resolves. That one closing is left as-is (undecorated/unfronted), and the remaining closings are decorated/composed as usual.

public interface IHandler<T> { }
public sealed class Handler<T> : IHandler<T> { }
public sealed class Logging<T>(IHandler<T> inner) : IHandler<T> where T : class; // reference types only

[Container]
[Transient(typeof(Handler<>), typeof(IHandler<>))]
[Transient<Root>]
[Decorate(typeof(Logging<>), typeof(IHandler<>))] // IHandler<int> can't take Logging<int>: warned and skipped
public static partial class CoffeeShop;

AWT127

Error

The typeof-argument form of a lifetime, [Decorate] or [Composite] attribute must receive an unbound open generic type.

[Container]
[Transient(typeof(Repository<Order>), typeof(IRepository<Order>))] // closed, not open
public static partial class CoffeeShop;

AWT128

Error

An open generic implementation does not expose its service with type parameters in declaration order. The open [Decorate]/[Composite] typeof forms are held to the same rule.

public sealed class Map<TKey, TValue> : IMap<TValue, TKey>; // parameters swapped

[Container]
[Transient(typeof(Map<,>), typeof(IMap<,>))]
public static partial class CoffeeShop;

AWT129

Error

Open generic expansion nested too deep, indicating an unbounded generic recursion.

public sealed class Box<T>(Box<Box<T>> inner); // Box<Order> needs Box<Box<Order>> needs ...
public sealed class Consumer(Box<Order> box);

[Container]
[Transient(typeof(Box<>))]
[Singleton<Consumer>]
public static partial class CoffeeShop;

Property injection

AWT136

Error

An [Inject] property has no set or init accessor the container can assign through.

public sealed class Barista
{
[Inject] public ITimeSystem? Time { get; } // get-only, nothing to assign
}

[Container]
[Transient<Barista>]
[Singleton<RealTimeSystem, ITimeSystem>]
public static partial class CoffeeShop;

AWT144

Error

An [Inject(Deferred = true)] property is init-only or required rather than assignable after construction.

public sealed class Node
{
[Inject(Deferred = true)] public Node? Peer { get; init; } // deferred needs a plain set
}

[Container]
[Singleton<Node>]
public static partial class CoffeeShop;

AWT145

Error

A deferred property cycle consists entirely of transients and cannot terminate.

public sealed class A { [Inject(Deferred = true)] public B? B { get; set; } }
public sealed class B { [Inject(Deferred = true)] public A? A { get; set; } }

[Container]
[Transient<A>] // all transient: a fresh instance every step, so the cycle never closes
[Transient<B>]
public static partial class CoffeeShop;

AWT146

Error

A deferred property cycle includes an async-initialized service and cannot terminate.

public sealed class A : IAsyncInitializable
{
[Inject(Deferred = true)] public B? B { get; set; }
public Task InitializeAsync(CancellationToken ct) => Task.CompletedTask;
}
public sealed class B { [Inject(Deferred = true)] public A? A { get; set; } }

[Container]
[Singleton<A>]
[Singleton<B>]
public static partial class CoffeeShop;

AWT147

Error

A deferred property cycle still traverses a construction-time edge that duplicates a cached participant or recurses forever.

public sealed class A(B b); // construction-time edge into the cycle
public sealed class B { [Inject(Deferred = true)] public A? A { get; set; } }

[Container]
[Transient<A>]
[Transient<B>]
public static partial class CoffeeShop;

AWT157

Error

An [Inject(Optional = true)] property is required and cannot be omitted from the object initializer.

public sealed class Barista
{
[Inject(Optional = true)] public required ITimeSystem Time { get; set; } // required cannot be omitted
}

[Container]
[Transient<Barista>]
public static partial class CoffeeShop;

AWT158

Warning

An [Inject(Optional = true)] property is init-only, so an unregistered dependency leaves it permanently at its default.

public sealed class Barista
{
[Inject(Optional = true)] public ITimeSystem? Time { get; init; } // init-only stays default forever
}

[Container]
[Transient<Barista>]
public static partial class CoffeeShop;

AWT177

Error

An [InjectProperty<T>] names a member that is not a settable property on the implementation: an unknown name, a field or method, or a read-only property.

public sealed class Barista
{
public ITimeSystem? Time { get; set; }
}

[Container]
[Singleton<RealTimeSystem, ITimeSystem>]
[Singleton<Barista>]
[InjectProperty<Barista>("Tim")] // no such property (typo); use nameof(Barista.Time)
public static partial class CoffeeShop;

AWT178

Error

An [InjectProperty<T>] targets an implementation produced by a Factory or Instance registration. Such an instance is built whole by its source, so there is no object initializer for the container to fill.

public sealed class Barista
{
public ITimeSystem? Time { get; set; }
}

[Container]
[Singleton<RealTimeSystem, ITimeSystem>]
[Singleton<Barista>(Factory = nameof(MakeBarista))]
[InjectProperty<Barista>(nameof(Barista.Time))] // Barista is factory-produced, not container-constructed
public static partial class CoffeeShop
{
private static Barista MakeBarista() => new();
}

AWT179

Warning

Two [InjectProperty<T>] entries name the same property of the same implementation. The property is filled once; the duplicate is ignored.

public sealed class Barista
{
public ITimeSystem? Time { get; set; }
}

[Container]
[Singleton<RealTimeSystem, ITimeSystem>]
[Singleton<Barista>]
[InjectProperty<Barista>(nameof(Barista.Time))]
[InjectProperty<Barista>(nameof(Barista.Time))] // filled once; the second entry is redundant
public static partial class CoffeeShop;

AWT180

Warning

An [InjectProperty<T>] names an implementation that has no container-constructed registration, so the entry is never applied: the type is unregistered, or it is an open generic no consumer closed.

public sealed class Barista
{
public ITimeSystem? Time { get; set; }
}

[Container]
[Singleton<RealTimeSystem, ITimeSystem>]
[InjectProperty<Barista>(nameof(Barista.Time))] // Barista itself is never registered
public static partial class CoffeeShop;

AWT181

Warning

A property carries both [Inject] and a container-side [InjectProperty<T>] entry. The property is filled once, from [Inject], so the entry's Optional/Deferred/Key are ignored.

public sealed class Barista
{
[Inject] public ITimeSystem? Time { get; set; }
}

[Container]
[Singleton<RealTimeSystem, ITimeSystem>]
[Singleton<Barista>]
[InjectProperty<Barista>(nameof(Barista.Time))] // [Inject] already fills it: remove one
public static partial class CoffeeShop;

Scanning

AWT138

Warning

A [Scan] matched no concrete type assignable to its marker.

public interface IPlugin; // no implementations exist

[Container]
[Scan<IPlugin>]
public static partial class CoffeeShop;

AWT139

Warning

A [Scan(As = ScanAs.Marker)] matched a type with no assignable interface.

public abstract class Report;
public sealed class QuarterlyReport : Report; // a Report, but implements no interface

[Container]
[Scan(typeof(Report), As = ScanAs.Marker)]
public static partial class CoffeeShop;

AWT140

Warning

A [Scan(InAssembliesOf = …)] named an assembly with no candidate types.

[Container]
[Scan(typeof(IPlugin), InAssembliesOf = new[] { typeof(string) })] // that assembly has no IPlugin types
public static partial class CoffeeShop;

AWT141

Warning

A [Scan(SkipUnconstructable = true)] match the container cannot construct is skipped.

public sealed class LegacyPlugin : IPlugin
{
private LegacyPlugin() { } // no usable constructor, so it is skipped
}

[Container]
[Scan(typeof(IPlugin), SkipUnconstructable = true)]
public static partial class CoffeeShop;

AWT142

Warning

Scans register one implementation with conflicting lifetimes.

public sealed class Widget : IPlugin, IHandler; // matched by both scans

[Container]
[Scan(typeof(IPlugin), Lifetime = AwaitenLifetime.Singleton)]
[Scan(typeof(IHandler), Lifetime = AwaitenLifetime.Transient)] // two lifetimes for Widget
public static partial class CoffeeShop;

AWT143

Error

A [Scan(InAssembliesOf = …)] resolved to no assembly at all.

[Container]
[Scan(typeof(IPlugin), InAssembliesOf = new Type[0])] // no assemblies to scan
public static partial class CoffeeShop;

AWT172

Warning

A [Scan]'s name, namespace or exclude filters removed every marker-assignable match.

public sealed class GrinderPlugin : IPlugin; // assignable, but filtered out by the pattern below

[Container]
[Scan<IPlugin>(NamePatterns = ["*Handler"])] // no IPlugin ends in "Handler", so nothing registers
public static partial class CoffeeShop;

AWT173

Warning

A [Scan] exclusion (an Exclude type or a !-prefixed pattern) matched no candidate.

public sealed class GrinderPlugin : IPlugin;

[Container]
[Scan<IPlugin>(Exclude = [typeof(RenamedPlugin)])] // RenamedPlugin is no longer a candidate: stale
public static partial class CoffeeShop;

AWT174

Warning

A [Scan] include pattern matches every candidate, so it does not narrow the scan.

[Container]
[Scan<IPlugin>(NamePatterns = ["*"])] // "*" matches every name; drop it
public static partial class CoffeeShop;

AWT182

Warning

A [Scan(As = ScanAs.MatchingInterface)] matched a type that implements no interface named I + its own name.

public interface IEspresso;
public sealed class Espresso : IEspresso;
public sealed class Ristretto : IEspresso; // no IRistretto, so it is not registered

[Container]
[Scan<IEspresso>(As = ScanAs.MatchingInterface)]
public static partial class CoffeeShop;

AWT183

Error

A markerless [Scan] includes the Marker exposure, or declares no scoping filter.

[Container]
[Scan(As = ScanAs.MatchingInterface)] // markerless, but no NamePatterns/NamespacePatterns/InAssembliesOf
public static partial class CoffeeShop;

AWT184

Warning

A markerless [Scan] matched candidate types but registered none of them.

[Container]
// Nothing under CoffeeShop.Legacy follows the I + name convention, so nothing registers.
[Scan(As = ScanAs.MatchingInterface, NamespacePatterns = ["CoffeeShop.Legacy.**"])]
public static partial class CoffeeShop;

AWT185

Error

A [Scan]'s As resolved to no ScanAs flag, so it would register nothing (usually & written for |).

[Container]
[Scan<IDrink>(As = ScanAs.Self & ScanAs.Marker)] // & is empty; use | to combine flags
public static partial class CoffeeShop;

AWT187

Warning

A [Scan(As = ScanAs.MatchingInterface)] matched a type implementing several same-named convention interfaces, so it registers under each of them.

namespace CoffeeShop.Old { public interface IMenu; }
namespace CoffeeShop.New { public interface IMenu; }

namespace CoffeeShop
{
public interface IShopService;

// No CoffeeShop.IMenu exists to win the own-namespace tiebreak, so Menu registers under both.
public sealed class Menu : IShopService, Old.IMenu, New.IMenu;

[Container]
[Scan<IShopService>(As = ScanAs.MatchingInterface)]
public static partial class Shop;
}

Reported only when several interfaces actually register (an inaccessible candidate is dropped, which AWT188 covers when the match registers nothing) and only for a marker scan: a markerless [Scan] registers the ambiguous match silently, like the other per-match scan warnings.

AWT188

Warning

A scan match's only exposure interface is inaccessible to the generated container, so the match is not registered.

// In a referenced assembly: the convention interface is internal.
internal interface IRoaster;
public sealed class Roaster : IRoaster, IEquipment;

// Registering Roaster under IRoaster would not compile in the container's assembly.
[Container]
[Scan<IEquipment>(As = ScanAs.MatchingInterface, InAssembliesOf = [typeof(IEquipment)])]
public static partial class CoffeeShop;

AWT193

Warning

A [Scan] matched a type that is inaccessible to the generated container, so it is not registered.

// In a referenced assembly: the implementation itself is internal.
public interface IEquipment;
internal sealed class Roaster : IEquipment;

// Roaster is assignable to the marker, but the container cannot name it to construct it.
[Container]
[Scan<IEquipment>(InAssembliesOf = [typeof(IEquipment)])]
public static partial class CoffeeShop;

Where AWT188 is about an interface the match cannot be exposed under, this one is about the match itself, so no ScanAs flag rescues it: every registration has to name the implementation. Make the type public, grant the container's assembly InternalsVisibleTo, or exclude it with a NamePatterns/NamespacePatterns entry (the Exclude type list cannot name an inaccessible type). Reported only for a type the marker matched and the scan's filters kept, so an unrelated internal type in a scanned assembly stays silent, as does one you already excluded.

AWT198

Error

A generic lifecycle hook on an open-generic [Scan] marker could bind a match through more than one closed marker form, so its type arguments are ambiguous.

public interface IView<TViewModel>;
public sealed class DualView : IView<Orders>, IView<Payments>; // closes IView<> twice

[Container]
[Scan(typeof(IView<>), As = ScanAs.Marker, OnActivated = nameof(Wire))]
public static partial class CoffeeShop
{
private static void Wire<TViewModel>(IView<TViewModel> view) { } // TViewModel would be Orders or Payments?
}

A generic scan hook binds its type argument from a closed marker form, so it needs exactly one it can bind. DualView, which closes IView<> at both Orders and Payments, offers two; the same happens when two scans bind the same generic hook through differently-closed markers of one type. Only forms the hook could actually bind count: matching arity, type arguments the generated container can access, satisfied constraints, and a first parameter that accepts the match. If exactly one form remains after those checks the hook binds it, so a where TViewModel : class constraint can settle a family that closes the marker at one class and one struct, and a closing at another assembly's internal type yields to an accessible sibling. Register the type explicitly with the intended hook, or split the family so only one closed form binds it. Reported only when the ambiguous generic method is the sole usable overload of the hook name: if another overload also accepts the match, the collision is between overloads rather than closings (settling the closings would still leave two usable overloads), so it is AWT190 instead. Only a generic hook is affected: a non-generic hook (its parameter typed as the marker or object) takes no type argument, so a match with several closings is fine for it, as is a match closing the marker several times without any hook (each closed form registers as its own collection member).

AWT199

Warning

Two [Scan] attributes match one implementation with conflicting lifecycle hooks; the first scan's hook is used.

public sealed class Widget : IPlugin, IHandler; // matched by both scans

[Container]
[Scan(typeof(IPlugin), OnActivated = nameof(PluginStarted))]
[Scan(typeof(IHandler), OnActivated = nameof(HandlerStarted))] // two OnActivated hooks for Widget
public static partial class CoffeeShop
{
private static void PluginStarted(object instance) { }
private static void HandlerStarted(object instance) { }
}

Which method ran for Widget would depend on attribute order, so the contradiction is surfaced instead, mirroring AWT142 for lifetimes. Overlapping scans that agree are fine: two scans naming the same method merge, and so do scans hooking different slots (one scan's OnActivated combines with another's OnRelease). A hook name is owner-relative, though: a module [Scan] resolves its hooks against the module and a container [Scan] against the container, so a module scan and a container scan naming the same method name still conflict, because each means its own method. An explicit registration of the type is not reported either: a scan yields to it wholesale, hooks included, so the explicit registration's hooks (or its deliberate lack of them) replace the scan's.

Modules

AWT149

Error

An [Import] names a type that is not marked [Module].

public static class PaymentHelpers; // not marked [Module]

[Container]
[Import(typeof(PaymentHelpers))]
public static partial class CoffeeShop;

AWT150

Error

An imported module has its own [Import], which is not followed.

[Module]
[Import(typeof(BaseModule))] // a nested import is not followed
public static class PaymentModule;

[Container]
[Import(typeof(PaymentModule))]
public static partial class CoffeeShop;

AWT151

Warning

An imported module declares no registrations.

[Module]
public static class EmptyModule; // nothing to contribute

[Container]
[Import(typeof(EmptyModule))]
public static partial class CoffeeShop;

AWT152

Error

An imported [Module] class is not declared static.

[Module]
public class PaymentModule; // must be static

AWT153

Error

A module Factory/Instance member is not accessible from the generated container.

[Module]
[Singleton<Grinder>(Factory = nameof(MakeGrinder))]
public static class EquipmentModule
{
private static Grinder MakeGrinder() => new(); // private, so the container cannot call it
}

AWT154

Error

An imported module declares a [Scan], but its assembly carries no generated expansion.

// In a referenced library that was compiled WITHOUT the Awaiten source generator:
[Module]
[Scan<IDrink>] // never expanded, so it would contribute nothing
public static partial class MenuModule;

[Container]
[Import(typeof(MenuModule))]
public static partial class CoffeeShop;

(Repurposed: this ID previously rejected any [Scan] on a module.) A [Scan] on a [Module] is self-compiled in the module's own build, which stamps the module with a generated marker even when the scan matched nothing. A module whose metadata carries a [Scan] but no marker was compiled without the Awaiten generator, or with a version predating self-compiled scans, so its scan would silently contribute nothing. Rebuild the library with the Awaiten generator referenced. Reported at the consumer's [Import]. The self-compilation constraints themselves are reported as AWT194AWT197, AWT200AWT202.

AWT155

Warning

Two imported modules strongly register the same service with different implementations.

[Module]
[Singleton<RealTimeSystem, ITimeSystem>]
public static class ModuleA;

[Module]
[Singleton<MockTimeSystem, ITimeSystem>]
public static class ModuleB;

[Container]
[Import(typeof(ModuleA))]
[Import(typeof(ModuleB))] // both strongly register ITimeSystem
public static partial class CoffeeShop;

AWT194

Error

A [Module] that declares a [Scan] is not partial (or is nested in a type that is not), so its scan cannot be self-compiled.

[Module]
[Scan<IPlugin>(As = ScanAs.MatchingInterface)]
public static class PluginModule; // must be partial to receive the generated factories

A self-compiled module scan emits a factory method and a registration attribute into the module's partial, re-opening the whole nesting chain of a nested module. Add the partial modifier to the module class and every type containing it. Reported in the module's own build.

AWT195

Error

A self-compiled module [Scan] match has a constructor parameter of a type inaccessible outside the module's assembly.

internal sealed class Secret;
internal sealed class Roaster(Secret secret) : IRoaster; // Secret is internal

[Module]
[Scan<IRoaster>(As = ScanAs.MatchingInterface)]
public static partial class PluginModule;

The generated factory is a public method whose parameters are resolved from the consuming container's graph, so each parameter type has to be nameable by the consumer. Widen the parameter type's accessibility, or exclude the match. (A v1 limitation: a self-compiled scan cannot construct a match through an inaccessible parameter.)

AWT196

Warning

A self-compiled module [Scan] match has no exposure interface accessible outside the module's assembly, so it is skipped.

internal sealed class Roaster : IPlugin;

[Module]
[Scan<IPlugin>(As = ScanAs.Self)] // Self exposes the internal type, which a consumer cannot name
public static partial class PluginModule;

A consumer resolves a self-compiled match only through an accessible interface. ScanAs.Self over an internal implementation exposes nothing nameable, so the match registers nothing and is skipped, mirroring the warning severity a container scan gives a match it cannot register (AWT182/AWT188/AWT193). The skip holds even when the importing container lives in the module's own assembly, so a module scan registers the same matches wherever the module is imported from. Expose it through a public interface (ScanAs.MatchingInterface or ScanAs.Marker), or exclude the match.

AWT197

Error

A self-compiled module [Scan] match would be exposed under more than one interface.

internal sealed class Roaster : IPlugin, IRoaster;

[Module]
[Scan<IPlugin>(As = ScanAs.Marker | ScanAs.MatchingInterface)] // IPlugin and IRoaster both apply
public static partial class PluginModule;

A self-compiled match is reached through a generated factory that returns a single accessible interface, so a shared instance across several interfaces cannot be expressed (unlike a container [Scan], whose matches coalesce on the concrete type). The same applies across scans: two [Scan]s on one module that expose the same type under two different interfaces would emit two factories and silently split the instance, so that overlap is also rejected. Narrow the exposure to a single interface (typically ScanAs.MatchingInterface), or exclude the match. This is a v1 limitation.

AWT200

Error

A self-compiled module [Scan] match carries injection metadata the generated factory cannot mirror.

internal sealed class Roaster : IRoaster
{
public Roaster(IClock clock) { }

[Inject] // keys, optionality and deferral live on this attribute
public IGrinder Grinder { get; set; }
}

[Module]
[Scan<IRoaster>(As = ScanAs.MatchingInterface)]
public static partial class PluginModule;

The generated factory reduces a match to a plain parameter list resolved from the consuming container's graph. An [Inject] property, or a constructor parameter marked [Inject] or [Arg], carries per-dependency semantics that plain parameters cannot express, so the consumer would silently construct the match differently than a container [Scan] would. Remove the attribute, exclude the match, or register the type through a hand-written module factory.

AWT201

Error

A generic [Module] (or one nested in a generic type) declares a [Scan].

[Module]
[Scan<IPlugin>(As = ScanAs.MatchingInterface)]
public static partial class PluginModule<T>; // no closed PluginModule<T> exists to [Import]

A consumer imports a module by typeof, so there is no single closed module type to import from a generic declaration, and the generated partial could not re-open it by its bare name. Move the [Scan] onto a non-generic module.

AWT202

Error

A module [Scan] declares InAssembliesOf, but a self-compiled scan sweeps only the module's own assembly.

[Module]
[Scan<IPlugin>(As = ScanAs.Marker, InAssembliesOf = new[] { typeof(OtherLibMarker) })]
public static partial class PluginModule;

A self-compiled scan exists to reach the module's own internal types. Sweeping another assembly from a module would see only that assembly's public types, which a container [Scan] with InAssembliesOf already covers, so the module form would silently do less than the container form. Remove InAssembliesOf to scan the module's own assembly, or move the [Scan] onto the container.

AWT203

Error

A self-compiled module [Scan] hook has a parameter, after the instance, whose type is not accessible outside the module's assembly.

internal sealed class Secret;
internal sealed class Roaster : IPlugin;

[Module]
[Scan<IPlugin>(As = ScanAs.MatchingInterface, OnActivated = nameof(Wire))]
public static partial class PluginModule
{
internal static void Wire(Roaster roaster, Secret secret) { } // Secret is internal to the module
}

The module emits a public wrapper for the hook, and that wrapper's parameters after the instance are resolved from the consuming container's graph, so each type has to be nameable by the consumer, the same rule a self-compiled factory parameter gets (AWT195). The instance parameter itself is exempt: it is the accessible exposure interface, cast back to the internal implementation inside the module. Widen the parameter type's accessibility, resolve a different dependency, or drop the hook.

AWT204

Error

A self-compiled module [Scan] hook has a parameter, after the instance, marked [FromKey] or [Inject].

internal sealed class Roaster : IPlugin;

[Module]
[Scan<IPlugin>(As = ScanAs.MatchingInterface, OnActivated = nameof(Wire))]
public static partial class PluginModule
{
internal static void Wire(Roaster roaster, [FromKey("main")] IClock clock) { }
}

The hook's parameters after the instance mirror onto the generated public wrapper as a bare type-and-name signature, so the attribute's per-dependency semantics (a key, optionality, deferral) would be silently dropped: a cross-assembly consumer would resolve the plain type while a same-compilation container, binding the module's own hook directly, honored the attribute, so the same source would inject different instances depending on which side of the assembly boundary the consumer sits. This is the hook-parameter twin of the factory's AWT200, rejected rather than silently degraded. Remove the attribute, resolve the dependency plainly, or drop the hook.

Keyed collections

AWT159

Error

A keyed dictionary (IReadOnlyDictionary<TKey, TService>) has a key type that is neither string nor an enum, or one whose keyed registrations do not all match it.

public sealed class Router(IReadOnlyDictionary<int, IMilk> milks); // the key must be string or an enum

[Container]
[Singleton<OatMilk, IMilk>(Key = "Oat")]
[Singleton<Router>]
public static partial class CoffeeShop;

AWT160

Error

A [FromKey] is applied to a synthesized keyed collection (IReadOnlyDictionary<string, TService>), which resolves every key.

public sealed class Router([FromKey("Oat")] IReadOnlyDictionary<string, IMilk> milks); // the map already holds every key

[Container]
[Singleton<OatMilk, IMilk>(Key = "Oat")]
[Singleton<Router>]
public static partial class CoffeeShop;

Contextual binding

AWT167

Warning

A WhenInjectedInto contextual binding never applies because the named consumer has no unkeyed direct constructor parameter to redirect.

public sealed class KitchenDisplay; // has no IReceiptPrinter parameter to redirect

[Container]
[Singleton<ThermalPrinter, IReceiptPrinter>]
[Singleton<WidePrinter, IReceiptPrinter>(WhenInjectedInto = typeof(KitchenDisplay))]
[Singleton<KitchenDisplay>]
public static partial class CoffeeShop;

AWT168

Error

A registration sets both WhenInjectedInto and Key. A contextual binding is reached only through its consumer, so the Key could never be selected by a [FromKey] and is silently dropped. Remove one of the two.

[Container]
[Singleton<ThermalPrinter, IReceiptPrinter>]
[Singleton<WidePrinter, IReceiptPrinter>(WhenInjectedInto = typeof(DriveThroughRegister), Key = "wide")] // Key is dropped
[Singleton<DriveThroughRegister>]
public static partial class CoffeeShop;

AWT169

Error

Two different implementations set WhenInjectedInto for the same service and consumer, so both claim the one contextual slot for that consumer.

[Container]
[Singleton<WidePrinter, IReceiptPrinter>(WhenInjectedInto = typeof(DriveThroughRegister))]
[Singleton<ThermalPrinter, IReceiptPrinter>(WhenInjectedInto = typeof(DriveThroughRegister))] // two bindings for one consumer
[Singleton<DriveThroughRegister>]
public static partial class CoffeeShop;

AWT170

Error

A [Key] or [FromKey] uses a constant whose type is not a supported key type. A resolution key must be a string, an enum value, or a typeof(...).

public sealed class LatteRecipe([FromKey(5)] IMilk milk); // int is not a supported key type

[Container]
[Singleton<OatMilk, IMilk>(Key = "Oat")]
[Singleton<LatteRecipe>]
public static partial class CoffeeShop;

External services

AWT175

Error

A type is declared [ImportService<T>] (drawn from the host provider) but is also registered on the container. A type is either host-owned or Awaiten-owned, not both.

[Container]
[ImportService<IPaymentGateway>] // declared external
[Singleton<StripeGateway, IPaymentGateway>] // …but is also registered, a contradiction
public static partial class CoffeeShop;

AWT176

Warning

A type declared [ImportService<T>] is never consumed by any dependency in the graph, so the declaration is dead. This is most often a stale or mistyped [ImportService<T>].

[Container]
[ImportService<IPaymentGateway>] // nothing in the graph depends on IPaymentGateway
[Singleton<Menu>]
public static partial class CoffeeShop;

Composition boundary

These guard the line between your domain and the container. They are suppressible warnings, reported by the analyzer rather than the generator, so a deliberate exception can opt out in source.

AWT134

Warning

A container-side composition attribute (a lifetime registration, [Scan], [Decorate], [Composite], [Import], [ImportService], or [InjectProperty]) is applied to a class in an assembly that declares no [Container]. Composition belongs on the [Container] (or an imported [Module]); domain code should stay free of it. This is a best-effort guard: it stays silent in an assembly that also declares the [Container], where the cross-assembly boundary is better enforced by an architecture test.

// A domain library that declares no [Container]:
[Singleton<EspressoMachine>] // registration belongs on the [Container], not here
public sealed class EspressoMachine;

AWT135

Warning

A resolver seam (IAwaitenResolver, IAwaitenScope, IAwaitenRoot, and the like) is injected into a type that is not the [Container] composition root. Resolving from the container at run time is the Service Locator anti-pattern: it hides the type's real dependencies and defeats the compile-time graph check. Inject the dependency you actually need instead, or, if the type is a host-integration adapter, suppress this on it with a justification. The typed fast-path IAwaitenResolver<T> is a single-service seam and is not reported.

public sealed class Barista(IAwaitenResolver resolver) // locates dependencies at run time
{
public Cup Serve() => resolver.Resolve<Cup>();
}

One kind of type is meant to hold a resolver: an adapter that bridges the container to a host's own dependency-injection surface, where holding it is the adaptation rather than a hidden run-time dependency. AwaitenServiceProvider is one, and anything you write against a framework's own registrar is another. This is an analyzer diagnostic, so suppress it in source, with the justification saying which adapter it is.

Below, IServiceLocator stands for whatever single-method resolution interface your framework declares, where returning null is how an adapter says "not mine". Both answers that are not a plain instance carry their weight: the collection convention every framework expects, and the withheld reason that keeps a container's own failure from being reported as absence.

using System.Diagnostics.CodeAnalysis;

[SuppressMessage("Awaiten", "AWT135:Service locator: a resolver interface is injected into a service",
Justification = "This is a custom bridge adapter: holding the scope is the adaptation itself, not a hidden run-time dependency.")]
internal sealed class AwaitenServiceLocator(IAwaitenContainerMetadata container, IAwaitenScope scope) : IServiceLocator
{
public object? Resolve(Type? type)
{
if (type is null)
{
return null;
}

if (scope.TryResolve(type, out object? instance))
{
return instance;
}

// Reported as absent, the framework would bind it from elsewhere and fail far from the cause.
if (container.WithheldReason(type, null) is { } reason)
{
throw new InvalidOperationException(reason);
}

return EmptyCollectionElement(type) is { } elementType
? Array.CreateInstance(elementType, 0)
: null;
}

/// <summary>
/// The element type to answer with an empty sequence rather than <see langword="null" />, because a
/// framework uses a collection as an extension point and "no handlers" still has to enumerate. A value
/// element would need an array type native AOT does not generate; a resolvable one has members an empty
/// sequence would drop.
/// </summary>
private Type? EmptyCollectionElement(Type type)
{
if (!type.IsConstructedGenericType || type.GetGenericTypeDefinition() != typeof(IEnumerable<>))
{
return null;
}

Type elementType = type.GenericTypeArguments[0];
return elementType.IsValueType || container.IsResolvable(elementType, null) ? null : elementType;
}
}

AwaitenServiceProvider is the same shape against MS.DI's IServiceProvider, so read it alongside this if your framework's surface is keyed or asks resolvability questions.

Suppress it on the adapter, never project-wide: the diagnostic is right about every other type.