From 6668c4c3bc60677049a6ad2cb8682d5d8ddf432d Mon Sep 17 00:00:00 2001 From: Jan-Hendrik Garber Date: Wed, 7 Oct 2026 21:19:22 +0200 Subject: [PATCH] fix: Analyze method dependencies for async lambdas. Resolves #230 Signed-off-by: Jan-Hendrik Garber --- ArchUnitNET/Loader/TypeProcessor.cs | 11 +++ .../Members/MethodCallDependencyTests.cs | 97 +++++++++++++++++- .../Members/MethodDependencyTestBuild.cs | 99 +++++++++++++++++++ 3 files changed, 206 insertions(+), 1 deletion(-) diff --git a/ArchUnitNET/Loader/TypeProcessor.cs b/ArchUnitNET/Loader/TypeProcessor.cs index 193812176..b966ad6ad 100644 --- a/ArchUnitNET/Loader/TypeProcessor.cs +++ b/ArchUnitNET/Loader/TypeProcessor.cs @@ -826,6 +826,17 @@ var calledMethodReference in calledMethodReferences.Except(visitedMethodReferenc ); } + if (calledMethodDefinition.IsAsync()) + { + HandleAsync( + out calledMethodDefinition, + ref calledMethodBody, + bodyTypes, + visitedMethodReferences, + domainResolver + ); + } + var calledScan = calledMethodDefinition.ScanMethodBody(domainResolver); bodyTypes.AddRange(calledScan.BodyTypes); castTypes.AddRange(calledScan.CastTypes); diff --git a/ArchUnitNETTests/Domain/Dependencies/Members/MethodCallDependencyTests.cs b/ArchUnitNETTests/Domain/Dependencies/Members/MethodCallDependencyTests.cs index 6e0ed9a7f..df49e098a 100644 --- a/ArchUnitNETTests/Domain/Dependencies/Members/MethodCallDependencyTests.cs +++ b/ArchUnitNETTests/Domain/Dependencies/Members/MethodCallDependencyTests.cs @@ -1,4 +1,6 @@ -using System.Linq; +using System; +using System.Linq; +using System.Threading.Tasks; using ArchUnitNET.Domain; using ArchUnitNET.Domain.Dependencies; using ArchUnitNET.Domain.Extensions; @@ -107,6 +109,17 @@ MethodCallDependency expectedDependency Assert.Contains(expectedDependency, originMember.GetMethodCallDependencies()); } + [Theory] + [ClassData(typeof(MethodDependencyTestBuild.MethodCallDependencyInAsyncLambdaTestData))] + public void MethodCallDependenciesAreFoundInAsyncLambdas( + IMember originMember, + MethodCallDependency expectedDependency + ) + { + Assert.True(originMember.HasMemberDependency(expectedDependency)); + Assert.Contains(expectedDependency, originMember.GetMethodCallDependencies()); + } + [Theory] [ClassData( typeof(MethodDependencyTestBuild.MethodCallGenericConstructorArgumentsDependencyTestData) @@ -173,6 +186,88 @@ public static async void MethodAAsync() } } + public class ClassWithAsyncLambdas + { + public static void MethodWithAsyncLambda() + { +#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously + Func asyncLambda = async () => +#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously + { + var classWithMethodB = new ClassWithMethodB(); + ClassWithMethodB.MethodB(); + }; + } + } + + public class ClassWithAsyncLambdaVariants + { + public static void MethodWithCapturingAsyncLambda() + { + var capturedValue = 0; +#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously + Func asyncLambda = async () => +#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously + { + capturedValue++; + var classWithMethodB = new ClassWithMethodB(); + ClassWithMethodB.MethodB(); + }; + } + + public static void MethodWithAsyncLocalFunction() + { + _ = LocalFunctionAsync(); + +#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously + async Task LocalFunctionAsync() +#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously + { + var classWithMethodB = new ClassWithMethodB(); + ClassWithMethodB.MethodB(); + } + } + + public static void MethodWithNestedAsyncLambda() + { + Func outerAsyncLambda = async () => + { +#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously + Func innerAsyncLambda = async () => +#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously + { + var classWithMethodB = new ClassWithMethodB(); + ClassWithMethodB.MethodB(); + }; + await innerAsyncLambda(); + }; + } + + public static async Task AsyncMethodWithAsyncLambda() + { +#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously + Func asyncLambda = async () => +#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously + { + var classWithMethodB = new ClassWithMethodB(); + ClassWithMethodB.MethodB(); + }; + await asyncLambda(); + } + + public static void GenericMethodWithAsyncLambda() + { +#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously + Func> asyncLambda = async () => +#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously + { + var classWithMethodB = new ClassWithMethodB(); + ClassWithMethodB.MethodB(); + return default; + }; + } + } + public class ClassWithMethodWithGenericConstructorArguments { public static void Method() diff --git a/ArchUnitNETTests/Domain/Dependencies/Members/MethodDependencyTestBuild.cs b/ArchUnitNETTests/Domain/Dependencies/Members/MethodDependencyTestBuild.cs index 60cdc7282..baa059a33 100644 --- a/ArchUnitNETTests/Domain/Dependencies/Members/MethodDependencyTestBuild.cs +++ b/ArchUnitNETTests/Domain/Dependencies/Members/MethodDependencyTestBuild.cs @@ -146,6 +146,105 @@ IEnumerator IEnumerable.GetEnumerator() } } + public class MethodCallDependencyInAsyncLambdaTestData : IEnumerable + { + private readonly List _methodCallDependencyData = new List + { + BuildMethodCallDependencyTestData( + typeof(ClassWithAsyncLambdas), + nameof(ClassWithAsyncLambdas.MethodWithAsyncLambda).BuildMethodMemberName(), + typeof(ClassWithMethodB), + StaticConstants.ConstructorNameBase.BuildMethodMemberName() + ), + BuildMethodCallDependencyTestData( + typeof(ClassWithAsyncLambdas), + nameof(ClassWithAsyncLambdas.MethodWithAsyncLambda).BuildMethodMemberName(), + typeof(ClassWithMethodB), + nameof(ClassWithMethodB.MethodB).BuildMethodMemberName() + ), + BuildMethodCallDependencyTestData( + typeof(ClassWithAsyncLambdaVariants), + nameof(ClassWithAsyncLambdaVariants.MethodWithCapturingAsyncLambda) + .BuildMethodMemberName(), + typeof(ClassWithMethodB), + StaticConstants.ConstructorNameBase.BuildMethodMemberName() + ), + BuildMethodCallDependencyTestData( + typeof(ClassWithAsyncLambdaVariants), + nameof(ClassWithAsyncLambdaVariants.MethodWithCapturingAsyncLambda) + .BuildMethodMemberName(), + typeof(ClassWithMethodB), + nameof(ClassWithMethodB.MethodB).BuildMethodMemberName() + ), + BuildMethodCallDependencyTestData( + typeof(ClassWithAsyncLambdaVariants), + nameof(ClassWithAsyncLambdaVariants.MethodWithAsyncLocalFunction) + .BuildMethodMemberName(), + typeof(ClassWithMethodB), + StaticConstants.ConstructorNameBase.BuildMethodMemberName() + ), + BuildMethodCallDependencyTestData( + typeof(ClassWithAsyncLambdaVariants), + nameof(ClassWithAsyncLambdaVariants.MethodWithAsyncLocalFunction) + .BuildMethodMemberName(), + typeof(ClassWithMethodB), + nameof(ClassWithMethodB.MethodB).BuildMethodMemberName() + ), + BuildMethodCallDependencyTestData( + typeof(ClassWithAsyncLambdaVariants), + nameof(ClassWithAsyncLambdaVariants.MethodWithNestedAsyncLambda) + .BuildMethodMemberName(), + typeof(ClassWithMethodB), + StaticConstants.ConstructorNameBase.BuildMethodMemberName() + ), + BuildMethodCallDependencyTestData( + typeof(ClassWithAsyncLambdaVariants), + nameof(ClassWithAsyncLambdaVariants.MethodWithNestedAsyncLambda) + .BuildMethodMemberName(), + typeof(ClassWithMethodB), + nameof(ClassWithMethodB.MethodB).BuildMethodMemberName() + ), + BuildMethodCallDependencyTestData( + typeof(ClassWithAsyncLambdaVariants), + nameof(ClassWithAsyncLambdaVariants.AsyncMethodWithAsyncLambda) + .BuildMethodMemberName(), + typeof(ClassWithMethodB), + StaticConstants.ConstructorNameBase.BuildMethodMemberName() + ), + BuildMethodCallDependencyTestData( + typeof(ClassWithAsyncLambdaVariants), + nameof(ClassWithAsyncLambdaVariants.AsyncMethodWithAsyncLambda) + .BuildMethodMemberName(), + typeof(ClassWithMethodB), + nameof(ClassWithMethodB.MethodB).BuildMethodMemberName() + ), + BuildMethodCallDependencyTestData( + typeof(ClassWithAsyncLambdaVariants), + nameof(ClassWithAsyncLambdaVariants.GenericMethodWithAsyncLambda) + .BuildMethodMemberName(), + typeof(ClassWithMethodB), + StaticConstants.ConstructorNameBase.BuildMethodMemberName() + ), + BuildMethodCallDependencyTestData( + typeof(ClassWithAsyncLambdaVariants), + nameof(ClassWithAsyncLambdaVariants.GenericMethodWithAsyncLambda) + .BuildMethodMemberName(), + typeof(ClassWithMethodB), + nameof(ClassWithMethodB.MethodB).BuildMethodMemberName() + ), + }; + + public IEnumerator GetEnumerator() + { + return _methodCallDependencyData.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + public class MethodSignatureDependencyTestData : IEnumerable { private readonly List _methodSignatureDependencyData = new List