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; + } +}