diff --git a/docs/architecture/source-generator.md b/docs/architecture/source-generator.md
index 1e4ef17f..86a3b331 100644
--- a/docs/architecture/source-generator.md
+++ b/docs/architecture/source-generator.md
@@ -27,6 +27,8 @@ Source Generator 是 AspectCore 的编译时代理引擎,基于 Roslyn 增量
- `IsProxyableClassProperty`(`:470`):同上(属性版)。
- 自动发现(程序集级)额外跳过含事件成员的类型、以及已显式标注的类型。
+程序集级自动发现的**接口**没有可推断的实现类型,默认生成**无目标 stub 代理**(成员返回 `default`,见下方「接口 stub」)。需要带实现的完整接口代理时,仍须在接口或实现类型上显式标注 `[AspectCoreGenerateProxy(typeof(Impl))]`。
+
## 3. 诊断(ACSGxxx)
生成器在遇到不支持的情况时报告诊断(`Emit/GeneratorDiagnostics.cs`,类别 `AspectCore.SourceGenerator`):
diff --git a/src/AspectCore.SourceGenerator/AspectCoreProxyGenerator.cs b/src/AspectCore.SourceGenerator/AspectCoreProxyGenerator.cs
index 79158778..2f9f7321 100644
--- a/src/AspectCore.SourceGenerator/AspectCoreProxyGenerator.cs
+++ b/src/AspectCore.SourceGenerator/AspectCoreProxyGenerator.cs
@@ -20,7 +20,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
static (node, _) => node is TypeDeclarationSyntax tds && tds.AttributeLists.Count > 0,
static (ctx, _) => GetCandidate(ctx))
.Where(static x => x is not null)
- .Select(static (x, _) => x!);
+ .Select(static (x, _) => new Candidate(x!, isExplicit: true));
// Also discover candidates from referenced assemblies (multi-assembly support)
var referencedAssemblyCandidates = context.CompilationProvider
@@ -38,16 +38,18 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
}
///
- /// Discovers types decorated with [AspectCoreGenerateProxy] in referenced assemblies.
- /// This enables multi-assembly scenarios where the attribute is placed in a referenced library.
+ /// Discovers proxy candidates in referenced assemblies. This enables multi-assembly
+ /// scenarios where the attribute is placed in a referenced library:
+ /// - assembly-level attribute there → auto-discover all eligible types (Explicit = false)
+ /// - type-level attribute on individual types → explicit candidates (Explicit = true)
///
- private static ImmutableArray GetReferencedAssemblyCandidates(Compilation compilation)
+ private static ImmutableArray GetReferencedAssemblyCandidates(Compilation compilation)
{
var attrSymbol = compilation.GetTypeByMetadataName(GenerateProxyAttributeMetadataName);
if (attrSymbol is null)
- return ImmutableArray.Empty;
+ return ImmutableArray.Empty;
- var results = new List();
+ var results = new List();
// Scan all referenced assemblies
foreach (var referencedAssembly in compilation.References)
@@ -62,20 +64,20 @@ private static ImmutableArray GetReferencedAssemblyCandidates(
if (hasAssemblyAttr)
{
- // Assembly-level: discover all eligible types
- foreach (var type in EnumerateAssemblyTypes(assemblySymbol.GlobalNamespace))
+ // Assembly-level: auto-discover all eligible types
+ foreach (var type in EnumerateTypes(assemblySymbol.GlobalNamespace))
{
- if (IsEligibleForAutoProxy(type))
- results.Add(type);
+ if (IsEligibleForAutoProxy(type, attrSymbol))
+ results.Add(new Candidate(type, isExplicit: false));
}
}
else
{
// Type-level: only discover types that explicitly carry the attribute
- foreach (var type in EnumerateAssemblyTypes(assemblySymbol.GlobalNamespace))
+ foreach (var type in EnumerateTypes(assemblySymbol.GlobalNamespace))
{
if (type.GetAttributes().Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, attrSymbol)))
- results.Add(type);
+ results.Add(new Candidate(type, isExplicit: true));
}
}
}
@@ -83,37 +85,60 @@ private static ImmutableArray GetReferencedAssemblyCandidates(
return results.ToImmutableArray();
}
- private static IEnumerable EnumerateAssemblyTypes(INamespaceSymbol ns)
+ private static IEnumerable EnumerateTypes(INamespaceSymbol ns)
{
foreach (var type in ns.GetTypeMembers())
yield return type;
foreach (var childNs in ns.GetNamespaceMembers())
{
- foreach (var type in EnumerateAssemblyTypes(childNs))
+ foreach (var type in EnumerateTypes(childNs))
yield return type;
}
}
- private static bool IsEligibleForAutoProxy(INamedTypeSymbol type)
+ ///
+ /// Determines whether is eligible for assembly-level auto-proxy
+ /// generation when its assembly declares [assembly: AspectCoreGenerateProxy].
+ /// Types that explicitly carry the type-level attribute are excluded here — they flow
+ /// through the explicit path and must keep their attribute metadata.
+ ///
+ private static bool IsEligibleForAutoProxy(INamedTypeSymbol type, INamedTypeSymbol attrSymbol)
{
- if (type.ContainingType is not null) return false; // skip nested
- if (type.IsStatic) return false;
- if (type.IsRefLikeType) return false; // skip ref structs (cannot be boxed/interfaced/class fields)
- if (type.DeclaredAccessibility is not (Accessibility.Public or Accessibility.Internal)) return false;
+ // Skip types that already have explicit type-level attribute (handled separately)
+ if (type.GetAttributes().Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, attrSymbol)))
+ return false;
+
+ // Skip nested types
+ if (type.ContainingType is not null)
+ return false;
+
+ // Skip static types (no instance members to intercept)
+ if (type.IsStatic)
+ return false;
+
+ // Skip ref structs (cannot be boxed / used as interface impl / class field)
+ if (type.IsRefLikeType)
+ return false;
+
+ // The generated proxy class is always public (ProxyEmitter hardcodes it), so auto-proxying
+ // is only possible for public source types. Internal types — even within the current
+ // compilation — would produce CS0060 (inconsistent accessibility): a public proxy cannot
+ // derive from or implement a less-accessible type. Referenced assemblies additionally only
+ // expose their public types to the consumer's generated code. The explicit path shares this
+ // limitation.
+ if (type.DeclaredAccessibility != Accessibility.Public)
+ return false;
+
+ // Skip types with events (event proxying is not supported by either engine)
+ if (type.GetMembers().OfType().Any())
+ return false;
if (type.TypeKind == TypeKind.Class)
{
- if (type.IsSealed && !type.IsAbstract) return false;
+ if (type.IsSealed && !type.IsAbstract)
+ return false;
// Must have at least one overridable member
- var isRecord = RecordTypeUtils.IsRecord(type);
- foreach (var member in type.GetMembers())
- {
- if (member is IMethodSymbol m && IsProxyableClassMethod(type, m, isRecord))
- return true;
- if (member is IPropertySymbol p && IsProxyableClassProperty(type, p, isRecord))
- return true;
- }
- return false;
+ return HasAnyOverridableMember(type);
}
if (type.TypeKind == TypeKind.Interface)
@@ -148,7 +173,7 @@ private static bool IsEligibleForAutoProxy(INamedTypeSymbol type)
return null;
}
- private static void Execute(SourceProductionContext context, Compilation compilation, ImmutableArray candidates)
+ private static void Execute(SourceProductionContext context, Compilation compilation, ImmutableArray candidates)
{
// Supports:
// - type-level [AspectCoreGenerateProxy]: explicit per-type proxy generation
@@ -167,23 +192,40 @@ private static void Execute(SourceProductionContext context, Compilation compila
var hasAssemblyLevelAttr = compilation.Assembly.GetAttributes()
.Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, attrSymbol));
- // Collect all candidate types: explicit type-level + auto-discovered from assembly-level
- var allCandidates = new HashSet(NamedTypeSymbolEqualityComparer.Instance);
- foreach (var t in candidates)
- allCandidates.Add(t);
-
+ // Explicit candidates carry the type-level attribute and keep their attribute metadata
+ // (e.g. the declared implementation type). Auto-discovered candidates from assembly-level
+ // auto-discovery carry none, so they are created with default proxy semantics below.
+ var explicitTypes = candidates
+ .Where(c => c.Explicit)
+ .Select(c => c.Type)
+ .Distinct(NamedTypeSymbolEqualityComparer.Instance)
+ .ToList();
+
+ var autoDiscovered = candidates
+ .Where(c => !c.Explicit)
+ .Select(c => c.Type)
+ .ToList();
+
+ // Auto-discover eligible types in the current assembly when it declares the assembly-level attribute.
+ // Note: iterate Assembly.GlobalNamespace (source-declared types only) — Compilation.GlobalNamespace
+ // also surfaces metadata types from referenced assemblies.
if (hasAssemblyLevelAttr)
{
- foreach (var t in GetAssemblyEligibleTypes(compilation, attrSymbol))
- allCandidates.Add(t);
+ foreach (var type in EnumerateTypes(compilation.Assembly.GlobalNamespace))
+ {
+ if (IsEligibleForAutoProxy(type, attrSymbol))
+ autoDiscovered.Add(type);
+ }
}
var entries = new List();
- foreach (var type in allCandidates.Distinct(NamedTypeSymbolEqualityComparer.Instance))
+ foreach (var type in explicitTypes)
{
var attrData = type.GetAttributes().FirstOrDefault(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, attrSymbol));
if (attrData is null)
{
+ // Defensive: explicit candidates always carry the attribute; keep the guard so
+ // the explicit path never treats an auto-discovered type as explicit.
continue;
}
@@ -282,6 +324,8 @@ private static void Execute(SourceProductionContext context, Compilation compila
}
}
+ CollectAutoDiscoveredEntries(context, entries, autoDiscovered);
+
if (entries.Count == 0)
{
return;
@@ -310,6 +354,37 @@ private static void Execute(SourceProductionContext context, Compilation compila
}
}
+ ///
+ /// Creates proxy entries for types discovered via assembly-level auto-discovery.
+ /// These types carry no type-level attribute, so entries are built with default
+ /// semantics: classes become class proxies (service = implementation = the type),
+ /// interfaces become no-target stub proxies.
+ ///
+ private static void CollectAutoDiscoveredEntries(
+ SourceProductionContext context, List entries, IEnumerable types)
+ {
+ foreach (var type in types.Distinct(NamedTypeSymbolEqualityComparer.Instance))
+ {
+ switch (type.TypeKind)
+ {
+ case TypeKind.Interface:
+ // No implementation type is known for auto-discovered interfaces;
+ // emit a no-target stub proxy (members return default).
+ entries.Add(ProxyEntry.CreateInterface(serviceType: type, implementationType: null));
+ break;
+
+ case TypeKind.Class:
+ if (!HasAccessibleConstructor(type))
+ {
+ context.ReportDiagnostic(GeneratorDiagnostics.NoAccessibleConstructor(type));
+ continue;
+ }
+ entries.Add(ProxyEntry.CreateClass(serviceType: type, implementationType: type));
+ break;
+ }
+ }
+ }
+
///
/// 检查类型是否对生成器可见(考虑 internal 和 InternalsVisibleTo)
///
@@ -374,72 +449,6 @@ private static bool HasAccessibleConstructor(INamedTypeSymbol type)
return false;
}
- ///
- /// Discovers all eligible types in the assembly for auto-proxy generation when
- /// [assembly: AspectCoreGenerateProxy] is used. Eligible types are public classes
- /// and interfaces that are not sealed (for classes), not nested, not abstract (for classes),
- /// and have at least one overridable member.
- ///
- private static IEnumerable GetAssemblyEligibleTypes(Compilation compilation, INamedTypeSymbol attrSymbol)
- {
- var globalNamespace = compilation.GlobalNamespace;
- foreach (var type in EnumerateAllTypes(globalNamespace))
- {
- // Skip types that already have explicit type-level attribute (they'll be handled separately)
- if (type.GetAttributes().Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, attrSymbol)))
- continue;
-
- // Skip nested types
- if (type.ContainingType is not null)
- continue;
-
- // Skip types that are not public or internal
- if (type.DeclaredAccessibility is not (Accessibility.Public or Accessibility.Internal))
- continue;
-
- // For classes: must not be sealed (unless abstract), and must have at least one overridable member
- if (type.TypeKind == TypeKind.Class)
- {
- if (type.IsSealed && !type.IsAbstract)
- continue;
- if (type.IsStatic)
- continue;
- // Check if there's at least one overridable method or property
- if (!HasAnyOverridableMember(type))
- continue;
- }
- else if (type.TypeKind == TypeKind.Interface)
- {
- // Interfaces are always eligible
- }
- else
- {
- continue;
- }
-
- // Skip types with events (not supported)
- if (type.GetMembers().OfType().Any())
- continue;
-
- yield return type;
- }
- }
-
- private static IEnumerable EnumerateAllTypes(INamespaceSymbol ns)
- {
- foreach (var type in ns.GetTypeMembers())
- {
- yield return type;
- }
- foreach (var childNs in ns.GetNamespaceMembers())
- {
- foreach (var type in EnumerateAllTypes(childNs))
- {
- yield return type;
- }
- }
- }
-
private static bool HasAnyOverridableMember(INamedTypeSymbol type)
{
var isRecord = RecordTypeUtils.IsRecord(type);
@@ -488,6 +497,24 @@ public int GetHashCode(INamedTypeSymbol obj)
=> SymbolEqualityComparer.Default.GetHashCode(obj);
}
+///
+/// A proxy candidate discovered by the generator. distinguishes
+/// types carrying the type-level [AspectCoreGenerateProxy] attribute from types
+/// auto-discovered via the assembly-level attribute. Auto-discovered types carry no
+/// attribute metadata, so their proxy entries are built with default semantics.
+///
+internal readonly struct Candidate
+{
+ public Candidate(INamedTypeSymbol type, bool isExplicit)
+ {
+ Type = type;
+ Explicit = isExplicit;
+ }
+
+ public INamedTypeSymbol Type { get; }
+ public bool Explicit { get; }
+}
+
internal enum ProxyKind
{
Interface = 0,
diff --git a/tests/AspectCore.Core.Tests/EngineParity/AssemblyLevelAutoProxyTests.cs b/tests/AspectCore.Core.Tests/EngineParity/AssemblyLevelAutoProxyTests.cs
new file mode 100644
index 00000000..463f5a98
--- /dev/null
+++ b/tests/AspectCore.Core.Tests/EngineParity/AssemblyLevelAutoProxyTests.cs
@@ -0,0 +1,315 @@
+#nullable enable
+
+using System;
+using System.Collections.Immutable;
+using System.IO;
+using System.Linq;
+using AspectCore.SourceGenerator;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Xunit;
+
+namespace AspectCore.Core.Tests.EngineParity;
+
+///
+/// Assembly-level [assembly: AspectCoreGenerateProxy] auto-discovery must actually
+/// produce proxies. Regression coverage for the Execute gate that used to drop every
+/// auto-discovered candidate (attrData is null → continue) — the auto-discovered
+/// types carry no type-level attribute, so the gate silently produced zero proxies.
+///
+public class AssemblyLevelAutoProxyTests
+{
+ private const string AssemblyLevelSource = """
+ using AspectCore.DynamicProxy;
+
+ [assembly: AspectCoreGenerateProxy]
+
+ namespace AutoProxy
+ {
+ public class CalcService
+ {
+ public virtual int Add(int a, int b) => a + b;
+ }
+
+ public interface ICalc
+ {
+ int Add(int a, int b);
+ }
+ }
+ """;
+
+ [Fact]
+ public void AssemblyLevelAttribute_AutoDiscoversClassAndInterface()
+ {
+ var (driver, generatorDiagnostics, compilationErrors) = RunGenerator(AssemblyLevelSource);
+
+ var generatedSources = driver.GetRunResult().Results[0].GeneratedSources;
+
+ // Class proxy for CalcService
+ Assert.Contains(generatedSources, s => s.HintName.Contains("CalcService") && s.HintName.Contains("ClassProxy"));
+ // No-target interface proxy for ICalc
+ Assert.Contains(generatedSources, s => s.HintName.Contains("ICalc"));
+ // A registry is emitted so the runtime can discover the proxies
+ Assert.Contains(generatedSources, s => s.HintName == "AspectCoreSourceGeneratedProxyRegistry.g.cs");
+
+ Assert.Empty(generatorDiagnostics.Where(d => d.Severity == DiagnosticSeverity.Error));
+ Assert.Empty(compilationErrors);
+ }
+
+ private const string IneligibleSource = """
+ using System;
+ using AspectCore.DynamicProxy;
+
+ [assembly: AspectCoreGenerateProxy]
+
+ namespace AutoProxy
+ {
+ public sealed class SealedService { public void Foo() {} }
+ public static class StaticService { public static void Foo() {} }
+ public class WithEvent { public virtual void Foo() {} public event Action E; }
+ public class NoMembers { }
+ public struct ValueThing { public void Foo() {} }
+ public class GoodService { public virtual void Foo() {} }
+ }
+ """;
+
+ [Fact]
+ public void AssemblyLevelAttribute_SkipsIneligibleTypes()
+ {
+ var (driver, _, compilationErrors) = RunGenerator(IneligibleSource);
+ var generatedSources = driver.GetRunResult().Results[0].GeneratedSources;
+
+ Assert.Contains(generatedSources, s => s.HintName.Contains("GoodService"));
+ Assert.DoesNotContain(generatedSources, s => s.HintName.Contains("SealedService"));
+ Assert.DoesNotContain(generatedSources, s => s.HintName.Contains("StaticService"));
+ Assert.DoesNotContain(generatedSources, s => s.HintName.Contains("WithEvent"));
+ Assert.DoesNotContain(generatedSources, s => s.HintName.Contains("NoMembers"));
+ Assert.DoesNotContain(generatedSources, s => s.HintName.Contains("ValueThing"));
+ Assert.Empty(compilationErrors);
+ }
+
+ private const string CoexistSource = """
+ using AspectCore.DynamicProxy;
+
+ [assembly: AspectCoreGenerateProxy]
+
+ namespace AutoProxy
+ {
+ public class AutoService { public virtual void Foo() {} }
+
+ [AspectCoreGenerateProxy]
+ public class ExplicitService { public virtual void Bar() {} }
+ }
+ """;
+
+ [Fact]
+ public void AssemblyLevelAttribute_CoexistsWithExplicitTypeLevelAttribute()
+ {
+ var (driver, generatorDiagnostics, compilationErrors) = RunGenerator(CoexistSource);
+ var generatedSources = driver.GetRunResult().Results[0].GeneratedSources;
+
+ Assert.Contains(generatedSources, s => s.HintName.Contains("AutoService"));
+ // ExplicitService is handled once via the explicit path and must not be
+ // duplicated by auto-discovery (IsEligibleForAutoProxy skips attributed types).
+ Assert.Single(generatedSources.Where(s => s.HintName.Contains("ExplicitService")));
+ Assert.Empty(generatorDiagnostics.Where(d => d.Severity == DiagnosticSeverity.Error));
+ Assert.Empty(compilationErrors);
+ }
+
+ [Fact]
+ public void ReferencedAssemblyWithAssemblyLevelAttribute_AutoDiscoversItsTypes()
+ {
+ const string libSource = """
+ using AspectCore.DynamicProxy;
+
+ [assembly: AspectCoreGenerateProxy]
+
+ namespace Lib
+ {
+ public class LibService
+ {
+ public virtual void Foo() {}
+ }
+
+ public interface ILib
+ {
+ void Foo();
+ }
+ }
+ """;
+
+ var references = CreateReferences();
+ var libReference = CompileLibrary(libSource, "AutoProxyLib");
+
+ // Main compilation does NOT declare the assembly-level attribute; it only references the lib.
+ const string mainSource = """
+ namespace Main
+ {
+ public class MainService { public virtual void Foo() {} }
+ }
+ """;
+
+ var mainCompilation = CSharpCompilation.Create(
+ assemblyName: "AutoProxyMain",
+ syntaxTrees: new[] { CSharpSyntaxTree.ParseText(mainSource, new CSharpParseOptions(LanguageVersion.CSharp13)) },
+ references: references.Concat(new[] { libReference }),
+ options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
+
+ GeneratorDriver driver = CSharpGeneratorDriver.Create(new AspectCoreProxyGenerator().AsSourceGenerator());
+ driver = driver.RunGeneratorsAndUpdateCompilation(mainCompilation, out var outputCompilation, out var generatorDiagnostics);
+ var generatedSources = driver.GetRunResult().Results[0].GeneratedSources;
+
+ Assert.Contains(generatedSources, s => s.HintName.Contains("LibService"));
+ Assert.Contains(generatedSources, s => s.HintName.Contains("ILib"));
+ // Main assembly has no assembly-level attribute → its own types are NOT auto-discovered
+ Assert.DoesNotContain(generatedSources, s => s.HintName.Contains("MainService"));
+ Assert.Empty(generatorDiagnostics.Where(d => d.Severity == DiagnosticSeverity.Error));
+ Assert.Empty(outputCompilation.GetDiagnostics().Where(d => d.Severity == DiagnosticSeverity.Error));
+ }
+
+ [Fact]
+ public void ReferencedAssemblyWithAssemblyLevelAttribute_SkipsInternalTypes()
+ {
+ const string libSource = """
+ using AspectCore.DynamicProxy;
+
+ [assembly: AspectCoreGenerateProxy]
+
+ namespace Lib
+ {
+ public class PublicService
+ {
+ public virtual void Foo() {}
+ }
+
+ internal class InternalService
+ {
+ public virtual void Bar() {}
+ }
+
+ internal interface IInternalService
+ {
+ void Baz();
+ }
+ }
+ """;
+
+ var references = CreateReferences();
+ var libReference = CompileLibrary(libSource, "AutoProxyLibWithInternal");
+
+ const string mainSource = """
+ namespace Main
+ {
+ public class MainService { public virtual void Foo() {} }
+ }
+ """;
+
+ var mainCompilation = CSharpCompilation.Create(
+ assemblyName: "AutoProxyMainWithInternal",
+ syntaxTrees: new[] { CSharpSyntaxTree.ParseText(mainSource, new CSharpParseOptions(LanguageVersion.CSharp13)) },
+ references: references.Concat(new[] { libReference }),
+ options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
+
+ GeneratorDriver driver = CSharpGeneratorDriver.Create(new AspectCoreProxyGenerator().AsSourceGenerator());
+ driver = driver.RunGeneratorsAndUpdateCompilation(mainCompilation, out var outputCompilation, out var generatorDiagnostics);
+ var generatedSources = driver.GetRunResult().Results[0].GeneratedSources;
+
+ // Public types of the referenced assembly are auto-discovered...
+ Assert.Contains(generatedSources, s => s.HintName.Contains("PublicService"));
+ // ...but internal types are NOT reachable from the consumer's generated code — proxying
+ // them would emit CS0122 in the consuming project (regression for the referenced-assembly
+ // assembly-level auto-discovery path).
+ Assert.DoesNotContain(generatedSources, s => s.HintName.Contains("InternalService"));
+ Assert.DoesNotContain(generatedSources, s => s.HintName.Contains("IInternalService"));
+ Assert.Empty(generatorDiagnostics.Where(d => d.Severity == DiagnosticSeverity.Error));
+ Assert.Empty(outputCompilation.GetDiagnostics().Where(d => d.Severity == DiagnosticSeverity.Error));
+ }
+
+ [Fact]
+ public void AssemblyLevelAttribute_SkipsInternalTypesInCurrentAssembly()
+ {
+ const string source = """
+ using AspectCore.DynamicProxy;
+
+ [assembly: AspectCoreGenerateProxy]
+
+ namespace AutoProxy
+ {
+ internal class InternalService
+ {
+ public virtual void Foo() {}
+ }
+
+ public class PublicService
+ {
+ public virtual void Foo() {}
+ }
+ }
+ """;
+
+ var (driver, generatorDiagnostics, compilationErrors) = RunGenerator(source);
+ var generatedSources = driver.GetRunResult().Results[0].GeneratedSources;
+
+ // The generated proxy class is always public (ProxyEmitter hardcodes it), so internal
+ // source types would produce CS0060 (inconsistent accessibility) — they are excluded from
+ // auto-discovery just like in referenced assemblies. Public types are still auto-discovered.
+ Assert.DoesNotContain(generatedSources, s => s.HintName.Contains("InternalService"));
+ Assert.Contains(generatedSources, s => s.HintName.Contains("PublicService"));
+ Assert.Empty(generatorDiagnostics.Where(d => d.Severity == DiagnosticSeverity.Error));
+ Assert.Empty(compilationErrors);
+ }
+
+ private static MetadataReference CompileLibrary(string libSource, string assemblyName)
+ {
+ var libCompilation = CSharpCompilation.Create(
+ assemblyName: assemblyName,
+ syntaxTrees: new[] { CSharpSyntaxTree.ParseText(libSource, new CSharpParseOptions(LanguageVersion.CSharp13)) },
+ references: CreateReferences(),
+ options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
+
+ using var ms = new MemoryStream();
+ var emitResult = libCompilation.Emit(ms);
+ Assert.True(emitResult.Success, string.Join("\n", emitResult.Diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error)));
+ // CreateFromImage keeps the bytes alive independently of the stream lifetime.
+ return MetadataReference.CreateFromImage(ms.ToArray());
+ }
+
+ private static (GeneratorDriver Driver, ImmutableArray GeneratorDiagnostics, ImmutableArray CompilationErrors) RunGenerator(string source)
+ {
+ var compilation = CSharpCompilation.Create(
+ assemblyName: "AutoProxyCompilation",
+ syntaxTrees: new[] { CSharpSyntaxTree.ParseText(source, new CSharpParseOptions(LanguageVersion.CSharp13)) },
+ references: CreateReferences(),
+ options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
+
+ GeneratorDriver driver = CSharpGeneratorDriver.Create(new AspectCoreProxyGenerator().AsSourceGenerator());
+ driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var generatorDiagnostics);
+
+ var compilationErrors = outputCompilation
+ .GetDiagnostics()
+ .Where(d => d.Severity == DiagnosticSeverity.Error)
+ .ToImmutableArray();
+
+ return (driver, generatorDiagnostics, compilationErrors);
+ }
+
+ private static System.Collections.Generic.List CreateReferences()
+ {
+ var references = AppDomain.CurrentDomain.GetAssemblies()
+ .Where(assembly => !assembly.IsDynamic
+ && !string.IsNullOrEmpty(assembly.Location)
+ && assembly != typeof(AssemblyLevelAutoProxyTests).Assembly)
+ .Select(assembly => MetadataReference.CreateFromFile(assembly.Location))
+ .ToList();
+
+ // The runtime package defining [AspectCoreGenerateProxy] may not be loaded into the
+ // test AppDomain yet; force it in so the generator can resolve the attribute symbol.
+ var attributeAssembly = typeof(AspectCore.DynamicProxy.AspectCoreGenerateProxyAttribute).Assembly;
+ if (references.All(r => r.Display != attributeAssembly.Location))
+ {
+ references.Add(MetadataReference.CreateFromFile(attributeAssembly.Location));
+ }
+
+ return references;
+ }
+}