From 47fa1f1d306253d11115e196454013ae7ac98b1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Breu=C3=9F?= Date: Mon, 29 Jun 2026 12:31:10 +0200 Subject: [PATCH 1/2] feat: warn (AWT106) when a synchronous factory hides an IAsyncInitializable type behind its declared return type A synchronous factory whose declared return type is plainer than the concrete type it builds hides that type's asynchronous initialization from the container: the container reads async-init taint off the declared return type, so a concrete IAsyncInitializable returned behind an interface is never initialized and is handed out uninitialized. This adds a best-effort, zero-false-positive lint that inspects the factory method's own return expressions (expression-bodied and block-bodied, never descending into nested lambdas or local functions) and fires only when the statically determined returned type is provably a non-abstract, non-interface named type that is async-initializable while the declared return type is not. Metadata-only factories and unanalyzable returns are left silent; helper-returned or runtime-selected implementations are accepted false negatives. The lint is scoped to the one capability with no other compile-time signal and no runtime safety net. A hidden IDisposable is not reported: the container already disposes factory outputs behind a runtime is-IDisposable check, so it does not leak. An asynchronous Task / ValueTask factory is not reported either: it is the explicit manual-initialization escape hatch, so the container awaits the factory and trusts it to initialize its result - matching how the container declines to drive InitializeAsync on an async factory's produced type. Severity Warning, since the lint has false negatives by design. The id reuses the retired AWT106 slot (the old per-registration disposable-transient check, removed before it ever shipped), filling the numbering gap. --- .../AnalyzerReleases.Unshipped.md | 1 + .../AwaitenGenerator.cs | 133 ++++++ .../Awaiten.SourceGenerators/Diagnostics.cs | 21 + ...s.Awt106FactoryHidesAsyncInitialization.cs | 416 ++++++++++++++++++ 4 files changed, 571 insertions(+) create mode 100644 Tests/Awaiten.SourceGenerators.Tests/DiagnosticTests.Awt106FactoryHidesAsyncInitialization.cs diff --git a/Source/Awaiten.SourceGenerators/AnalyzerReleases.Unshipped.md b/Source/Awaiten.SourceGenerators/AnalyzerReleases.Unshipped.md index 125bce3..342dc07 100644 --- a/Source/Awaiten.SourceGenerators/AnalyzerReleases.Unshipped.md +++ b/Source/Awaiten.SourceGenerators/AnalyzerReleases.Unshipped.md @@ -7,6 +7,7 @@ AWT103 | Awaiten | Error | An implementation type is abstract or an interface AWT104 | Awaiten | Error | An implementation type has no accessible constructor AWT105 | Awaiten | Error | A singleton captures a shorter-lived scoped dependency + AWT106 | Awaiten | Warning | A synchronous factory's body provably produces an IAsyncInitializable concrete type its declared return type hides AWT107 | Awaiten | Error | An implementation is registered with conflicting lifetimes AWT108 | Awaiten | Error | A Factory registration names a member that is not a usable factory method AWT109 | Awaiten | Error | An Instance registration names a member that is not a usable instance member diff --git a/Source/Awaiten.SourceGenerators/AwaitenGenerator.cs b/Source/Awaiten.SourceGenerators/AwaitenGenerator.cs index d4f5ce9..bd41c94 100644 --- a/Source/Awaiten.SourceGenerators/AwaitenGenerator.cs +++ b/Source/Awaiten.SourceGenerators/AwaitenGenerator.cs @@ -494,6 +494,16 @@ private static Dictionary> BuildDependencyGraph( // IAsyncInitializable, the container additionally awaits its InitializeAsync after the factory completes. bool asyncInit = asyncInitializableSymbol is not null && ImplementsInterface(disposalType, asyncInitializableSymbol); + // Best-effort lint (AWT106): a synchronous factory whose declared return type hides the asynchronous + // initialization its body provably produces. The container reads async-init taint off producer.ReturnType + // (above), so a concrete IAsyncInitializable returned behind a plainer interface is never initialized. + // An async Task/ValueTask factory owns its own initialization (the container awaits the factory), + // and a hidden IDisposable is disposed at runtime via RuntimeDisposalCheck - neither is reported. + if (info.Production == ProductionKind.Factory && !asyncFactory) + { + ReportFactoryHidingAsyncInitialization(producer, compilation, asyncInitializableSymbol, diagnostics); + } + return new InstanceModel( info.ImplementationType, info.Symbol.Name, @@ -524,6 +534,129 @@ static bool CouldHideDisposable(ITypeSymbol type) || (type.TypeKind == TypeKind.Class && !type.IsSealed); } + /// + /// Reports AWT106 when a synchronous + /// factory method's body provably returns a concrete type that implements IAsyncInitializable + /// while the method's declared return type does not - so the initialization is invisible to the + /// container and never runs. + /// + /// + /// Conservative by design: it inspects only the producer's own return expressions (both + /// expression-bodied and block-bodied), never descending into nested lambdas or local functions, and + /// fires only when the statically determined type of the returned expression is a non-abstract, + /// non-interface named type that is async-initializable. A metadata-only factory (no syntax) or an + /// unresolved/unanalyzable return type yields no diagnostic. False negatives (helper-returned or + /// runtime-selected implementations) are accepted; false positives are not. A hidden IDisposable + /// is not reported (the container disposes factory outputs behind a runtime check); an asynchronous + /// factory is excluded by the caller (it owns its own initialization). + /// + private static void ReportFactoryHidingAsyncInitialization( + IMethodSymbol producer, + Compilation compilation, + INamedTypeSymbol? asyncInitializableSymbol, + List diagnostics) + { + if (asyncInitializableSymbol is null) + { + return; + } + + ITypeSymbol declaredReturnType = producer.ReturnType; + + // The container already sees the initialization when the declared return type is itself + // async-initializable, so nothing it hides could be missed - there is no diagnostic to report. + if (Implements(declaredReturnType, asyncInitializableSymbol)) + { + return; + } + + // Already-reported concrete types: a factory with several returns of the same hidden type should + // surface a single diagnostic, not one per return. + HashSet reported = new(SymbolEqualityComparer.Default); + + foreach (SyntaxReference reference in producer.DeclaringSyntaxReferences) + { + // A factory must be a method on the container; anything else (or metadata-only, no syntax) is + // not analyzable here and is left silent. + if (reference.GetSyntax() is not MethodDeclarationSyntax method) + { + continue; + } + + SemanticModel model = compilation.GetSemanticModel(method.SyntaxTree); + + foreach (ExpressionSyntax returnExpression in CollectReturnExpressions(method)) + { + ITypeSymbol? returnedType = model.GetTypeInfo(returnExpression).Type; + if (returnedType is not INamedTypeSymbol concrete + || concrete.TypeKind == TypeKind.Interface + || concrete.IsAbstract + || concrete.TypeKind == TypeKind.Error + || !reported.Add(concrete) + || !Implements(concrete, asyncInitializableSymbol)) + { + continue; + } + + diagnostics.Add(new DiagnosticInfo( + Diagnostics.FactoryHidesAsyncInitialization, + LocationInfo.From(returnExpression.GetLocation()), + new EquatableArray([ + producer.Name, + Display(concrete.ToDisplayString(FullyQualified)), + Display(declaredReturnType.ToDisplayString(FullyQualified)), + ]))); + } + } + + static bool Implements(ITypeSymbol type, INamedTypeSymbol @interface) + { + return SymbolEqualityComparer.Default.Equals(type, @interface) + || type.AllInterfaces.Any(implemented => SymbolEqualityComparer.Default.Equals(implemented, @interface)); + } + } + + /// + /// The expressions a method directly returns: the arrow expression of an expression-bodied method, or + /// every return x; in a block body. Nested lambdas and local functions are not descended into, + /// so their returns are never attributed to the enclosing factory. + /// + private static IEnumerable CollectReturnExpressions(MethodDeclarationSyntax method) + { + if (method.ExpressionBody?.Expression is { } arrow) + { + yield return arrow; + yield break; + } + + if (method.Body is null) + { + yield break; + } + + Stack pending = new(); + pending.Push(method.Body); + while (pending.Count > 0) + { + SyntaxNode node = pending.Pop(); + foreach (SyntaxNode child in node.ChildNodes()) + { + // Do not cross into a nested function: its returns belong to it, not to the factory. + if (child is AnonymousFunctionExpressionSyntax or LocalFunctionStatementSyntax) + { + continue; + } + + if (child is ReturnStatementSyntax { Expression: { } returned }) + { + yield return returned; + } + + pending.Push(child); + } + } + } + /// /// Classifies the producer's parameters (a constructor's or a factory method's) and reports /// AWT101 for any non-[Arg] parameter whose diff --git a/Source/Awaiten.SourceGenerators/Diagnostics.cs b/Source/Awaiten.SourceGenerators/Diagnostics.cs index a68117a..0a647d6 100644 --- a/Source/Awaiten.SourceGenerators/Diagnostics.cs +++ b/Source/Awaiten.SourceGenerators/Diagnostics.cs @@ -65,6 +65,27 @@ internal static class Diagnostics DiagnosticSeverity.Error, isEnabledByDefault: true); + /// + /// A synchronous Factory method's body provably constructs (or returns a local of) a concrete + /// type that implements IAsyncInitializable, while its declared return type does not expose it. + /// The container reads async-initialization taint off the declared return type, so it cannot see that + /// the produced instance needs initialization - the hidden InitializeAsync never runs and the + /// instance is handed out uninitialized. A hidden IDisposable is not reported: the + /// container disposes factory outputs behind a runtime check, so it does not leak. An asynchronous + /// Task<T> / ValueTask<T> factory is not reported either: it owns its + /// own initialization (the container awaits the factory itself). This is a best-effort lint (Warning): + /// it fires only when the concrete returned type is statically provable from the body, so it has false + /// negatives by design (a helper-returned or runtime-selected implementation is not reported), but is + /// intended to have no false positives. + /// + public static readonly DiagnosticDescriptor FactoryHidesAsyncInitialization = new( + "AWT106", + "Factory hides asynchronous initialization behind its declared return type", + "factory '{0}' constructs '{1}', which is async-initialized, but declares return type '{2}'; the container cannot see that it needs initialization, so its InitializeAsync never runs. Return '{1}', or make the factory 'async Task<{2}>' and handle initialization inside it.", + "Awaiten", + DiagnosticSeverity.Warning, + isEnabledByDefault: true); + /// /// The same implementation is registered with more than one lifetime; coalescing into a single /// instance would silently drop one of the declared lifetimes. diff --git a/Tests/Awaiten.SourceGenerators.Tests/DiagnosticTests.Awt106FactoryHidesAsyncInitialization.cs b/Tests/Awaiten.SourceGenerators.Tests/DiagnosticTests.Awt106FactoryHidesAsyncInitialization.cs new file mode 100644 index 0000000..ba37726 --- /dev/null +++ b/Tests/Awaiten.SourceGenerators.Tests/DiagnosticTests.Awt106FactoryHidesAsyncInitialization.cs @@ -0,0 +1,416 @@ +using System.Linq; + +namespace Awaiten.SourceGenerators.Tests; + +public partial class DiagnosticTests +{ + public class Awt106FactoryHidesAsyncInitialization + { + [Fact] + public async Task ReportsWhenTheFactoryConstructsAnAsyncInitializableConcreteType() + { + GeneratorResult result = Generator.Run(""" + using Awaiten; + using System.Threading; + using System.Threading.Tasks; + + namespace MyCode; + + public interface IFoo { } + + public sealed class FooImpl : IFoo, IAsyncInitializable + { + public Task InitializeAsync(CancellationToken cancellationToken) => Task.CompletedTask; + } + + [Container] + [Singleton(Factory = nameof(Create))] + public static partial class MyContainer + { + private static IFoo Create() => new FooImpl(); + } + """); + + await That(result.Diagnostics).Contains("*AWT106*").AsWildcard() + .Because("the factory provably constructs an IAsyncInitializable type its declared return type hides"); + + string diagnostic = result.Diagnostics.Single(d => d.Contains("AWT106")); + await That(diagnostic).Contains("MyCode.FooImpl") + .Because("the message names the concrete type the author should return"); + await That(diagnostic).Contains("MyCode.IFoo") + .Because("the message names the declared return type that hides the capability"); + await That(diagnostic).Contains("async-initialized") + .Because("the message identifies the hidden capability as asynchronous initialization"); + } + + [Fact] + public async Task ReportsWhenTheBodyReturnsALocalOfTheConcreteType() + { + GeneratorResult result = Generator.Run(""" + using Awaiten; + using System.Threading; + using System.Threading.Tasks; + + namespace MyCode; + + public interface IFoo { } + + public sealed class FooImpl : IFoo, IAsyncInitializable + { + public Task InitializeAsync(CancellationToken cancellationToken) => Task.CompletedTask; + } + + [Container] + [Singleton(Factory = nameof(Create))] + public static partial class MyContainer + { + private static IFoo Create() + { + FooImpl foo = new FooImpl(); + return foo; + } + } + """); + + await That(result.Diagnostics).Contains("*AWT106*").AsWildcard() + .Because("the returned local's inferred type is the hidden concrete type"); + } + + [Fact] + public async Task ReportsASingleDiagnostic_WhenSeveralReturnsYieldTheSameHiddenType() + { + GeneratorResult result = Generator.Run(""" + using Awaiten; + using System.Threading; + using System.Threading.Tasks; + + namespace MyCode; + + public interface IFoo { } + + public sealed class FooImpl : IFoo, IAsyncInitializable + { + public Task InitializeAsync(CancellationToken cancellationToken) => Task.CompletedTask; + } + + [Container] + [Singleton(Factory = nameof(Create))] + public static partial class MyContainer + { + private static IFoo Create() + { + FooImpl foo = new FooImpl(); + if (foo.ToString().Length > 100) + { + return new FooImpl(); + } + + return foo; + } + } + """); + + await That(result.Diagnostics.Count(d => d.Contains("AWT106"))).IsEqualTo(1) + .Because("several returns of the same hidden concrete type surface one diagnostic, not one per return"); + } + + [Fact] + public async Task DoesNotReport_WhenTheDeclaredReturnTypeAlreadyExposesTheCapability() + { + GeneratorResult result = Generator.Run(""" + using Awaiten; + using System.Threading; + using System.Threading.Tasks; + + namespace MyCode; + + public sealed class FooImpl : IAsyncInitializable + { + public Task InitializeAsync(CancellationToken cancellationToken) => Task.CompletedTask; + } + + [Container] + [Singleton(Factory = nameof(Create))] + public static partial class MyContainer + { + private static FooImpl Create() => new FooImpl(); + } + """); + + await That(result.Diagnostics).DoesNotContain("*AWT106*").AsWildcard(); + } + + [Fact] + public async Task DoesNotReport_WhenTheDeclaredInterfaceExtendsIAsyncInitializable() + { + GeneratorResult result = Generator.Run(""" + using Awaiten; + using System.Threading; + using System.Threading.Tasks; + + namespace MyCode; + + public interface IFooAsync : IAsyncInitializable { } + + public sealed class FooImpl : IFooAsync + { + public Task InitializeAsync(CancellationToken cancellationToken) => Task.CompletedTask; + } + + [Container] + [Singleton(Factory = nameof(Create))] + public static partial class MyContainer + { + private static IFooAsync Create() => new FooImpl(); + } + """); + + await That(result.Diagnostics).DoesNotContain("*AWT106*").AsWildcard(); + } + + [Fact] + public async Task DoesNotReport_WhenTheBodyReturnsANonSpecialType() + { + GeneratorResult result = Generator.Run(""" + using Awaiten; + + namespace MyCode; + + public interface IFoo { } + + public sealed class FooImpl : IFoo { } + + [Container] + [Singleton(Factory = nameof(Create))] + public static partial class MyContainer + { + private static IFoo Create() => new FooImpl(); + } + """); + + await That(result.Diagnostics).DoesNotContain("*AWT106*").AsWildcard(); + } + + [Fact] + public async Task DoesNotReport_WhenTheConcreteTypeIsNotStaticallyProvableFromTheBody() + { + GeneratorResult result = Generator.Run(""" + using Awaiten; + using System.Threading; + using System.Threading.Tasks; + + namespace MyCode; + + public interface IFoo { } + + public sealed class FooImpl : IFoo, IAsyncInitializable + { + public Task InitializeAsync(CancellationToken cancellationToken) => Task.CompletedTask; + } + + [Container] + [Singleton(Factory = nameof(Create))] + public static partial class MyContainer + { + private static IFoo Build() => new FooImpl(); + private static IFoo Create() => Build(); + } + """); + + // The body returns the value of a helper call typed as IFoo; the concrete FooImpl is not provable + // without interprocedural analysis, so the lint stays silent. + await That(result.Diagnostics).DoesNotContain("*AWT106*").AsWildcard(); + } + + [Fact] + public async Task DoesNotReport_ForAConstructedRegistrationWithoutAFactory() + { + GeneratorResult result = Generator.Run(""" + using Awaiten; + using System.Threading; + using System.Threading.Tasks; + + namespace MyCode; + + public sealed class FooImpl : IAsyncInitializable + { + public Task InitializeAsync(CancellationToken cancellationToken) => Task.CompletedTask; + } + + [Container] + [Singleton] + public static partial class MyContainer + { + } + """); + + // No factory: the container constructs the type directly and already sees its capability. + await That(result.Diagnostics).DoesNotContain("*AWT106*").AsWildcard(); + } + + [Fact] + public async Task DoesNotReport_WhenANestedLambdaReturnsTheConcreteType() + { + GeneratorResult result = Generator.Run(""" + using Awaiten; + using System; + using System.Threading; + using System.Threading.Tasks; + + namespace MyCode; + + public interface IFoo { } + + public sealed class FooImpl : IFoo, IAsyncInitializable + { + public Task InitializeAsync(CancellationToken cancellationToken) => Task.CompletedTask; + } + + [Container] + [Singleton(Factory = nameof(Create))] + public static partial class MyContainer + { + private static IFoo Create() + { + Func make = () => new FooImpl(); + IFoo result = make(); + return result; + } + } + """); + + // The 'new FooImpl()' belongs to the nested lambda, not the factory's own return; the factory + // returns an IFoo-typed local, so nothing is provable. + await That(result.Diagnostics).DoesNotContain("*AWT106*").AsWildcard(); + } + + [Fact] + public async Task DoesNotReport_ForAnAsyncTaskFactory_WhichOwnsItsInitialization() + { + GeneratorResult result = Generator.Run(""" + using Awaiten; + using System.Threading; + using System.Threading.Tasks; + + namespace MyCode; + + public interface IFoo { } + + public sealed class FooImpl : IFoo, IAsyncInitializable + { + public Task InitializeAsync(CancellationToken cancellationToken) => Task.CompletedTask; + } + + [Container] + [Singleton(Factory = nameof(Create))] + public static partial class MyContainer + { + private static async Task Create() + { + FooImpl foo = new FooImpl(); + await foo.InitializeAsync(default); + return foo; + } + } + """); + + // An async Task factory is the explicit manual-initialization escape hatch: the container awaits + // the factory and does not drive InitializeAsync itself, so the lint must not nag a correct one. + await That(result.Diagnostics).DoesNotContain("*AWT106*").AsWildcard(); + } + + [Fact] + public async Task DoesNotReport_ForAnAsyncValueTaskFactory_WhichOwnsItsInitialization() + { + GeneratorResult result = Generator.Run(""" + using Awaiten; + using System.Threading; + using System.Threading.Tasks; + + namespace MyCode; + + public interface IFoo { } + + public sealed class FooImpl : IFoo, IAsyncInitializable + { + public Task InitializeAsync(CancellationToken cancellationToken) => Task.CompletedTask; + } + + [Container] + [Singleton(Factory = nameof(Create))] + public static partial class MyContainer + { + private static async ValueTask Create() + { + FooImpl foo = new FooImpl(); + await foo.InitializeAsync(default); + return foo; + } + } + """); + + await That(result.Diagnostics).DoesNotContain("*AWT106*").AsWildcard(); + } + + [Fact] + public async Task DoesNotReport_ForAHiddenDisposable_SinceTheContainerDisposesFactoryOutputs() + { + GeneratorResult result = Generator.Run(""" + using Awaiten; + using System; + + namespace MyCode; + + public interface IFoo { } + + public sealed class FooImpl : IFoo, IDisposable + { + public void Dispose() { } + } + + [Container] + [Singleton(Factory = nameof(Create))] + public static partial class MyContainer + { + private static IFoo Create() => new FooImpl(); + } + """); + + // A hidden IDisposable is not reported: the container disposes factory outputs behind a runtime + // `is IDisposable` check (RuntimeDisposalCheck), so there is no leak to warn about. + await That(result.Diagnostics).DoesNotContain("*AWT106*").AsWildcard(); + } + + [Fact] + public async Task ReportsAsyncInitialization_WhenTheHiddenTypeIsAlsoDisposable() + { + GeneratorResult result = Generator.Run(""" + using Awaiten; + using System; + using System.Threading; + using System.Threading.Tasks; + + namespace MyCode; + + public interface IFoo { } + + public sealed class FooImpl : IFoo, IAsyncInitializable, IDisposable + { + public Task InitializeAsync(CancellationToken cancellationToken) => Task.CompletedTask; + public void Dispose() { } + } + + [Container] + [Singleton(Factory = nameof(Create))] + public static partial class MyContainer + { + private static IFoo Create() => new FooImpl(); + } + """); + + string diagnostic = result.Diagnostics.Single(d => d.Contains("AWT106")); + await That(diagnostic).Contains("async-initialized") + .Because("async initialization - not disposal - is the unfixable capability the lint reports"); + } + } +} From 144b59ef8f84c1a2455c434a5e3536141088ce0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Breu=C3=9F?= Date: Mon, 29 Jun 2026 19:01:59 +0200 Subject: [PATCH 2/2] Reduce complexity --- .../AwaitenGenerator.cs | 79 +++++++++++-------- 1 file changed, 47 insertions(+), 32 deletions(-) diff --git a/Source/Awaiten.SourceGenerators/AwaitenGenerator.cs b/Source/Awaiten.SourceGenerators/AwaitenGenerator.cs index bd41c94..9daafba 100644 --- a/Source/Awaiten.SourceGenerators/AwaitenGenerator.cs +++ b/Source/Awaiten.SourceGenerators/AwaitenGenerator.cs @@ -419,39 +419,11 @@ private static Dictionary> BuildDependencyGraph( } // Select the producer: a container method (Factory) or the implementation's constructor (the - // default). A factory produces the instance, so the registered type may be an interface and is - // not subject to the not-instantiable check that a constructed type is. - IMethodSymbol? producer; - if (info.Production == ProductionKind.Factory) - { - producer = ResolveFactory(containerSymbol, info, compilation, diagnostics); - if (producer is null) - { - return null; - } - } - else + // default). A null result means the registration is unusable and a diagnostic was already reported. + IMethodSymbol? producer = SelectProducer(info, containerSymbol, compilation, serviceToImpl, diagnostics); + if (producer is null) { - // An abstract type or interface cannot be constructed; reject it instead of emitting a 'new' - // against it (which would fail to compile in the generated source). - if (info.Symbol.IsAbstract || info.Symbol.TypeKind == TypeKind.Interface) - { - diagnostics.Add(new DiagnosticInfo( - Diagnostics.NotInstantiable, - info.Location, - new EquatableArray([Display(info.ImplementationType),]))); - return null; - } - - producer = SelectConstructor(info.Symbol, containerSymbol, serviceToImpl.Keys.Select(k => k.Service)); - if (producer is null) - { - diagnostics.Add(new DiagnosticInfo( - Diagnostics.NoAccessibleConstructor, - info.Location, - new EquatableArray([Display(info.ImplementationType),]))); - return null; - } + return null; } // An asynchronous factory returns Task / ValueTask: the container awaits it, so the type it @@ -534,6 +506,49 @@ static bool CouldHideDisposable(ITypeSymbol type) || (type.TypeKind == TypeKind.Class && !type.IsSealed); } + /// + /// Selects the method that produces an implementation: a container method for a Factory + /// registration, or the implementation's own constructor otherwise. Returns + /// when the registration is unusable - an unresolved factory (AWT108), a non-instantiable abstract or + /// interface type (AWT103), or a type with no accessible constructor (AWT104) - having already appended + /// the corresponding diagnostic. A factory produces the instance, so the registered type may be an + /// interface and is not subject to the not-instantiable check a constructed type is. + /// + private static IMethodSymbol? SelectProducer( + ImplInfo info, + INamedTypeSymbol containerSymbol, + Compilation compilation, + Dictionary serviceToImpl, + List diagnostics) + { + if (info.Production == ProductionKind.Factory) + { + return ResolveFactory(containerSymbol, info, compilation, diagnostics); + } + + // An abstract type or interface cannot be constructed; reject it instead of emitting a 'new' + // against it (which would fail to compile in the generated source). + if (info.Symbol.IsAbstract || info.Symbol.TypeKind == TypeKind.Interface) + { + diagnostics.Add(new DiagnosticInfo( + Diagnostics.NotInstantiable, + info.Location, + new EquatableArray([Display(info.ImplementationType),]))); + return null; + } + + IMethodSymbol? constructor = SelectConstructor(info.Symbol, containerSymbol, serviceToImpl.Keys.Select(k => k.Service)); + if (constructor is null) + { + diagnostics.Add(new DiagnosticInfo( + Diagnostics.NoAccessibleConstructor, + info.Location, + new EquatableArray([Display(info.ImplementationType),]))); + } + + return constructor; + } + /// /// Reports AWT106 when a synchronous /// factory method's body provably returns a concrete type that implements IAsyncInitializable