From b2d35729389a598fbd442242bada30379ba49c10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tau=20G=C3=A4rtli?= Date: Mon, 13 Jan 2025 14:23:47 +0100 Subject: [PATCH 1/2] WIP: Suggest inlining of simple lambda expressions --- .../SimpleLambdaExpressionsTest.cs | 179 ++++++++++++++++++ .../SimpleLambdaExpressionsAnalyzer.cs | 73 +++++++ .../InlineSimpleLambdaExpressionsAttribute.cs | 7 + 3 files changed, 259 insertions(+) create mode 100644 Funcky.Analyzers/Funcky.Analyzers.Test/SimpleLambdaExpressionsTest.cs create mode 100644 Funcky.Analyzers/Funcky.Analyzers/SimpleLambdaExpressionsAnalyzer.cs create mode 100644 Funcky/CodeAnalysis/InlineSimpleLambdaExpressionsAttribute.cs diff --git a/Funcky.Analyzers/Funcky.Analyzers.Test/SimpleLambdaExpressionsTest.cs b/Funcky.Analyzers/Funcky.Analyzers.Test/SimpleLambdaExpressionsTest.cs new file mode 100644 index 00000000..1151387c --- /dev/null +++ b/Funcky.Analyzers/Funcky.Analyzers.Test/SimpleLambdaExpressionsTest.cs @@ -0,0 +1,179 @@ +using Xunit; +using VerifyCS = Funcky.Analyzers.Test.CSharpAnalyzerVerifier; + +namespace Funcky.Analyzers.Test; + +// TODO: Code Fix: Add cast for null literal +public sealed class SimpleLambdaExpressionsTest +{ + private const string FuncWithAttributeCode = @" +namespace Funcky.CodeAnalysis +{ + internal sealed class InlineSimpleLambdaExpressionsAttribute : System.Attribute + { + } +} + +public static partial class C +{ + public static void TakesFunc([Funcky.CodeAnalysis.InlineSimpleLambdaExpressions] System.Func func) { } +}"; + + [Fact] + public async Task Literal() + { + const string inputCode = @" +public static partial class C +{ + public static void M() + { + TakesFunc(() => 10); + } +}"; + await VerifyCS.VerifyAnalyzerAsync(inputCode + Environment.NewLine + FuncWithAttributeCode, VerifyCS.Diagnostic().WithSpan(6, 19, 6, 27)); + } + + [Fact] + public async Task Variable() + { + const string inputCode = @" +public static partial class C +{ + public static void M() + { + var variable = 10; + TakesFunc(() => variable); + } +}"; + await VerifyCS.VerifyAnalyzerAsync(inputCode + Environment.NewLine + FuncWithAttributeCode, VerifyCS.Diagnostic().WithSpan(7, 19, 7, 33)); + } + + [Fact] + public async Task ConstantValue() + { + const string inputCode = @" +public static partial class C +{ + public const int MemberConstant = 10; + + public static void M() + { + const int localConstant = 10; + TakesFunc(() => localConstant); + TakesFunc(() => MemberConstant); + TakesFunc(() => localConstant + 1); + } +}"; + await VerifyCS.VerifyAnalyzerAsync( + inputCode + Environment.NewLine + FuncWithAttributeCode, + VerifyCS.Diagnostic().WithSpan(9, 19, 9, 38), + VerifyCS.Diagnostic().WithSpan(10, 19, 10, 39), + VerifyCS.Diagnostic().WithSpan(11, 19, 11, 42)); + } + + [Fact] + public async Task Parameter() + { + const string inputCode = @" +public static partial class C +{ + public static void M(int parameter) + { + TakesFunc(() => parameter); + } +}"; + await VerifyCS.VerifyAnalyzerAsync(inputCode + Environment.NewLine + FuncWithAttributeCode, VerifyCS.Diagnostic().WithSpan(6, 19, 6, 34)); + } + + [Fact] + public async Task Cast() + { + const string inputCode = @" +public static partial class C +{ + public static void M(int parameter) + { + TakesFunc(() => (object)parameter); + } +}"; + await VerifyCS.VerifyAnalyzerAsync(inputCode + Environment.NewLine + FuncWithAttributeCode, VerifyCS.Diagnostic().WithSpan(6, 19, 6, 42)); + } + + [Fact] + public async Task ObjectCreation() + { + const string inputCode = @" +public static partial class C +{ + public static void M(int parameter) + { + TakesFunc(() => new object()); + } +}"; + await VerifyCS.VerifyAnalyzerAsync(inputCode + Environment.NewLine + FuncWithAttributeCode, VerifyCS.Diagnostic().WithSpan(6, 19, 6, 37)); + } + + [Fact] + public async Task ObjectCreationCounterExample() + { + const string inputCode = @" +public static partial class C +{ + public static void M(int parameter) + { + TakesFunc(() => new Foo(GetBar())); + TakesFunc(() => new Foo(0) { Bar = GetBar() }); + } + + public static int GetBar() { return 0; } + + public sealed class Foo + { + public Foo() { } + + public Foo(int bar) { } + + public int Bar { get; set; } + } +}"; + await VerifyCS.VerifyAnalyzerAsync(inputCode + Environment.NewLine + FuncWithAttributeCode); + } + + [Fact] + public async Task StaticProperty() + { + const string inputCode = @" +public static partial class C +{ + public static void M(int parameter) + { + TakesFunc(() => Foo.Value); + } + + public static class Foo + { + public static int Value { get; } + } +}"; + await VerifyCS.VerifyAnalyzerAsync(inputCode + Environment.NewLine + FuncWithAttributeCode, VerifyCS.Diagnostic().WithSpan(6, 19, 6, 34)); + } + + [Fact] + public async Task StaticField() + { + const string inputCode = @" +public static partial class C +{ + public static void M(int parameter) + { + TakesFunc(() => Foo.Value); + } + + public static class Foo + { + public static readonly int Value; + } +}"; + await VerifyCS.VerifyAnalyzerAsync(inputCode + Environment.NewLine + FuncWithAttributeCode, VerifyCS.Diagnostic().WithSpan(6, 19, 6, 34)); + } +} diff --git a/Funcky.Analyzers/Funcky.Analyzers/SimpleLambdaExpressionsAnalyzer.cs b/Funcky.Analyzers/Funcky.Analyzers/SimpleLambdaExpressionsAnalyzer.cs new file mode 100644 index 00000000..8daf5250 --- /dev/null +++ b/Funcky.Analyzers/Funcky.Analyzers/SimpleLambdaExpressionsAnalyzer.cs @@ -0,0 +1,73 @@ +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; + +namespace Funcky.Analyzers; + +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class SimpleLambdaExpressionsAnalyzer : DiagnosticAnalyzer +{ + public const string DiagnosticId = $"{DiagnosticName.Prefix}{DiagnosticName.Usage}05"; + + private const string AttributeFullName = "Funcky.CodeAnalysis.InlineSimpleLambdaExpressionsAttribute"; + + private static readonly DiagnosticDescriptor Descriptor = new DiagnosticDescriptor( + id: DiagnosticId, + title: "Simple lambda expression can be inlined", + messageFormat: "Simple lambda expression can be inlined", + description: "TODO.", + category: nameof(Funcky), + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Descriptor); + + public override void Initialize(AnalysisContext context) + { + context.EnableConcurrentExecution(); + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.RegisterCompilationStartAction(OnCompilationStarted); + } + + private static void OnCompilationStarted(CompilationStartAnalysisContext context) + { + if (context.Compilation.GetTypeByMetadataName(AttributeFullName) is { } attributeType) + { + context.RegisterOperationAction(AnalyzeArgument(attributeType), OperationKind.Argument); + } + } + + private static Action AnalyzeArgument(INamedTypeSymbol attributeType) + => context + => + { + var operation = (IArgumentOperation)context.Operation; + if (operation.Parameter is { } parameter + && operation.Value is IDelegateCreationOperation { Target: IAnonymousFunctionOperation lambda } + && parameter.GetAttributes().Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, attributeType)) + && MatchBlockOperationWithSingleReturn(lambda.Body) is { } returnedValue + && IsCheapOperation(returnedValue)) + { + context.ReportDiagnostic(Diagnostic.Create(Descriptor, lambda.Syntax.GetLocation())); + } + }; + + private static IOperation? MatchBlockOperationWithSingleReturn(IOperation operation) + => operation is IBlockOperation block + && block.Operations.Length == 1 + && block.Operations[0] is IReturnOperation @return + ? @return.ReturnedValue + : null; + + private static bool IsCheapOperation(IOperation operation) + => operation switch + { + IParameterReferenceOperation or ILocalReferenceOperation or ILiteralOperation => true, + IConversionOperation conversion => IsCheapOperation(conversion.Operand), + IObjectCreationOperation objectCreation => objectCreation.Children.All(IsCheapOperation), + IFieldReferenceOperation fieldReference => fieldReference.Field.IsStatic, + IPropertyReferenceOperation propertyReference => propertyReference.Property.IsStatic, + _ => operation.ConstantValue.HasValue, + }; +} diff --git a/Funcky/CodeAnalysis/InlineSimpleLambdaExpressionsAttribute.cs b/Funcky/CodeAnalysis/InlineSimpleLambdaExpressionsAttribute.cs new file mode 100644 index 00000000..d58471bb --- /dev/null +++ b/Funcky/CodeAnalysis/InlineSimpleLambdaExpressionsAttribute.cs @@ -0,0 +1,7 @@ +namespace Funcky.CodeAnalysis; + +/// The analyzer will suggest inlining lambda expressions passed to this method when the lambda is «simple». +[AttributeUsage(AttributeTargets.Parameter)] +internal sealed class InlineSimpleLambdaExpressionsAttribute : Attribute +{ +} From 69f76d6bca90272aa4a4e84191bd0aa6ec0974f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tau=20G=C3=A4rtli?= Date: Mon, 13 Jan 2025 14:30:27 +0100 Subject: [PATCH 2/2] Stash Version 2 --- .../SimpleLambdaExpressionsTest.cs | 291 +++++++++++------- .../SimpleLambdaExpressionsAnalyzer.cs | 84 +++-- 2 files changed, 231 insertions(+), 144 deletions(-) diff --git a/Funcky.Analyzers/Funcky.Analyzers.Test/SimpleLambdaExpressionsTest.cs b/Funcky.Analyzers/Funcky.Analyzers.Test/SimpleLambdaExpressionsTest.cs index 1151387c..ecc27e06 100644 --- a/Funcky.Analyzers/Funcky.Analyzers.Test/SimpleLambdaExpressionsTest.cs +++ b/Funcky.Analyzers/Funcky.Analyzers.Test/SimpleLambdaExpressionsTest.cs @@ -1,3 +1,4 @@ +using Microsoft.CodeAnalysis; using Xunit; using VerifyCS = Funcky.Analyzers.Test.CSharpAnalyzerVerifier; @@ -6,174 +7,234 @@ namespace Funcky.Analyzers.Test; // TODO: Code Fix: Add cast for null literal public sealed class SimpleLambdaExpressionsTest { - private const string FuncWithAttributeCode = @" -namespace Funcky.CodeAnalysis -{ - internal sealed class InlineSimpleLambdaExpressionsAttribute : System.Attribute - { - } -} - -public static partial class C -{ - public static void TakesFunc([Funcky.CodeAnalysis.InlineSimpleLambdaExpressions] System.Func func) { } -}"; + private const string FuncWithAttributeCode = + """ + public static class F + { + public static void TakesFunc(T value) { } + public static void TakesFunc(System.Func func) { } + public static void TakesFunc(System.Func func) { } + } + """; [Fact] public async Task Literal() { - const string inputCode = @" -public static partial class C -{ - public static void M() - { - TakesFunc(() => 10); - } -}"; - await VerifyCS.VerifyAnalyzerAsync(inputCode + Environment.NewLine + FuncWithAttributeCode, VerifyCS.Diagnostic().WithSpan(6, 19, 6, 27)); + const string inputCode = + """ + public static class C + { + public static void M() + { + F.TakesFunc(() => 10); + } + } + """; + await VerifyCS.VerifyAnalyzerAsync(inputCode + Environment.NewLine + FuncWithAttributeCode, VerifyCS.Diagnostic().WithSpan(5, 19, 5, 27)); } [Fact] public async Task Variable() { - const string inputCode = @" -public static partial class C -{ - public static void M() - { - var variable = 10; - TakesFunc(() => variable); - } -}"; - await VerifyCS.VerifyAnalyzerAsync(inputCode + Environment.NewLine + FuncWithAttributeCode, VerifyCS.Diagnostic().WithSpan(7, 19, 7, 33)); + const string inputCode = + """ + public static class C + { + public static void M() + { + var variable = 10; + F.TakesFunc(() => variable); + } + } + """; + await VerifyCS.VerifyAnalyzerAsync(inputCode + Environment.NewLine + FuncWithAttributeCode, VerifyCS.Diagnostic().WithSpan(6, 19, 6, 33)); } [Fact] public async Task ConstantValue() { - const string inputCode = @" -public static partial class C -{ - public const int MemberConstant = 10; - - public static void M() - { - const int localConstant = 10; - TakesFunc(() => localConstant); - TakesFunc(() => MemberConstant); - TakesFunc(() => localConstant + 1); - } -}"; + const string inputCode = + """ + public static class C + { + public const int MemberConstant = 10; + + public static void M() + { + const int localConstant = 10; + F.TakesFunc(() => localConstant); + F.TakesFunc(() => MemberConstant); + F.TakesFunc(() => localConstant + 1); + } + } + """; await VerifyCS.VerifyAnalyzerAsync( inputCode + Environment.NewLine + FuncWithAttributeCode, - VerifyCS.Diagnostic().WithSpan(9, 19, 9, 38), - VerifyCS.Diagnostic().WithSpan(10, 19, 10, 39), - VerifyCS.Diagnostic().WithSpan(11, 19, 11, 42)); + VerifyCS.Diagnostic().WithSpan(8, 19, 8, 38), + VerifyCS.Diagnostic().WithSpan(9, 19, 9, 39), + VerifyCS.Diagnostic().WithSpan(10, 19, 10, 42)); } [Fact] public async Task Parameter() { - const string inputCode = @" -public static partial class C -{ - public static void M(int parameter) - { - TakesFunc(() => parameter); + const string inputCode = + """ + public static class C + { + public static void M(int parameter) + { + F.TakesFunc(() => parameter); + } + } + """; + await VerifyCS.VerifyAnalyzerAsync(inputCode + Environment.NewLine + FuncWithAttributeCode, VerifyCS.Diagnostic().WithSpan(5, 19, 5, 34)); } -}"; - await VerifyCS.VerifyAnalyzerAsync(inputCode + Environment.NewLine + FuncWithAttributeCode, VerifyCS.Diagnostic().WithSpan(6, 19, 6, 34)); + + [Fact] + public async Task FuncWithParameter() + { + const string inputCode = + """ + public static class C + { + public static void M(int parameter) + { + F.TakesFunc(_ => parameter); + F.TakesFunc(lambdaParameter => lambdaParameter); + } + } + """; + await VerifyCS.VerifyAnalyzerAsync(inputCode + Environment.NewLine + FuncWithAttributeCode, VerifyCS.Diagnostic().WithSpan(5, 19, 5, 39)); } [Fact] public async Task Cast() { - const string inputCode = @" -public static partial class C -{ - public static void M(int parameter) - { - TakesFunc(() => (object)parameter); - } -}"; - await VerifyCS.VerifyAnalyzerAsync(inputCode + Environment.NewLine + FuncWithAttributeCode, VerifyCS.Diagnostic().WithSpan(6, 19, 6, 42)); + const string inputCode = + """ + public static class C + { + public static void M(int parameter) + { + F.TakesFunc(() => (object)parameter); + } + } + """; + await VerifyCS.VerifyAnalyzerAsync(inputCode + Environment.NewLine + FuncWithAttributeCode, VerifyCS.Diagnostic().WithSpan(5, 19, 5, 42)); } [Fact] public async Task ObjectCreation() { - const string inputCode = @" -public static partial class C -{ - public static void M(int parameter) - { - TakesFunc(() => new object()); + const string inputCode = + """ + public static class C + { + public static void M(int parameter) + { + F.TakesFunc(() => new object()); + } + } + """; + await VerifyCS.VerifyAnalyzerAsync(inputCode + Environment.NewLine + FuncWithAttributeCode, VerifyCS.Diagnostic().WithSeverity(DiagnosticSeverity.Info).WithSpan(5, 19, 5, 37)); } -}"; - await VerifyCS.VerifyAnalyzerAsync(inputCode + Environment.NewLine + FuncWithAttributeCode, VerifyCS.Diagnostic().WithSpan(6, 19, 6, 37)); + + [Fact] + public async Task AnonymousObjectCreation() + { + const string inputCode = + """ + public static class C + { + public static void M(int parameter) + { + F.TakesFunc(() => new { X = 10 }); + F.TakesFunc(() => new { X = 10, Y = "foo" }); + F.TakesFunc(() => new { }); + F.TakesFunc(() => new { X = new object() }); + F.TakesFunc(() => new { X = 10, Y = GetBar() }); + } + + public static int GetBar() { return 0; } + } + """; + + await VerifyCS.VerifyAnalyzerAsync( + inputCode + Environment.NewLine + FuncWithAttributeCode, + VerifyCS.Diagnostic().WithSpan(5, 19, 5, 39), + VerifyCS.Diagnostic().WithSpan(6, 19, 6, 50), + VerifyCS.Diagnostic().WithSpan(7, 19, 7, 32), + VerifyCS.Diagnostic().WithSeverity(DiagnosticSeverity.Info).WithSpan(8, 19, 8, 49)); } [Fact] public async Task ObjectCreationCounterExample() { - const string inputCode = @" -public static partial class C -{ - public static void M(int parameter) - { - TakesFunc(() => new Foo(GetBar())); - TakesFunc(() => new Foo(0) { Bar = GetBar() }); - } + const string inputCode = + """ + public static class C + { + public static void M(int parameter) + { + F.TakesFunc(() => new Foo(GetBar())); + F.TakesFunc(() => new Foo(0) { Bar = GetBar() }); + } - public static int GetBar() { return 0; } + public static int GetBar() { return 0; } - public sealed class Foo - { - public Foo() { } + public sealed class Foo + { + public Foo() { } - public Foo(int bar) { } + public Foo(int bar) { } - public int Bar { get; set; } - } -}"; + public int Bar { get; set; } + } + } + """; await VerifyCS.VerifyAnalyzerAsync(inputCode + Environment.NewLine + FuncWithAttributeCode); } [Fact] public async Task StaticProperty() { - const string inputCode = @" -public static partial class C -{ - public static void M(int parameter) - { - TakesFunc(() => Foo.Value); - } + const string inputCode = + """ + public static partial class C + { + public static void M(int parameter) + { + F.TakesFunc(() => Foo.Value); + } - public static class Foo - { - public static int Value { get; } - } -}"; - await VerifyCS.VerifyAnalyzerAsync(inputCode + Environment.NewLine + FuncWithAttributeCode, VerifyCS.Diagnostic().WithSpan(6, 19, 6, 34)); + public static class Foo + { + public static int Value { get; } + } + } + """; + await VerifyCS.VerifyAnalyzerAsync(inputCode + Environment.NewLine + FuncWithAttributeCode, VerifyCS.Diagnostic().WithSeverity(DiagnosticSeverity.Info).WithSpan(5, 19, 5, 34)); } [Fact] public async Task StaticField() { - const string inputCode = @" -public static partial class C -{ - public static void M(int parameter) - { - TakesFunc(() => Foo.Value); - } - - public static class Foo - { - public static readonly int Value; - } -}"; - await VerifyCS.VerifyAnalyzerAsync(inputCode + Environment.NewLine + FuncWithAttributeCode, VerifyCS.Diagnostic().WithSpan(6, 19, 6, 34)); + const string inputCode = + """ + public static partial class C + { + public static void M(int parameter) + { + F.TakesFunc(() => Foo.Value); + } + + public static class Foo + { + public static readonly int Value; + } + } + """; + await VerifyCS.VerifyAnalyzerAsync(inputCode + Environment.NewLine + FuncWithAttributeCode, VerifyCS.Diagnostic().WithSpan(5, 19, 5, 34)); } } diff --git a/Funcky.Analyzers/Funcky.Analyzers/SimpleLambdaExpressionsAnalyzer.cs b/Funcky.Analyzers/Funcky.Analyzers/SimpleLambdaExpressionsAnalyzer.cs index 8daf5250..ee5e4e8b 100644 --- a/Funcky.Analyzers/Funcky.Analyzers/SimpleLambdaExpressionsAnalyzer.cs +++ b/Funcky.Analyzers/Funcky.Analyzers/SimpleLambdaExpressionsAnalyzer.cs @@ -32,42 +32,68 @@ public override void Initialize(AnalysisContext context) private static void OnCompilationStarted(CompilationStartAnalysisContext context) { - if (context.Compilation.GetTypeByMetadataName(AttributeFullName) is { } attributeType) + context.RegisterOperationAction(AnalyzeArgument, OperationKind.Argument); + } + + private static void AnalyzeArgument(OperationAnalysisContext context) + { + var operation = (IArgumentOperation)context.Operation; + if (operation.Parameter is { } parameter + && operation.Value is IDelegateCreationOperation { Target: IAnonymousFunctionOperation lambda } + && parameter.ContainingSymbol is IMethodSymbol { ContainingType: var containingType } method + && MatchBlockOperationWithSingleReturn(lambda.Body) is { } returnedValue + && containingType.GetMembers().OfType().Any(m => m.Name == method.Name + && SymbolEqualityComparer.IncludeNullability.Equals(m.ReturnType, method.ReturnType) + && m.Parameters.Length == 1 + && SymbolEqualityComparer.IncludeNullability.Equals(m.Parameters[0].Type, returnedValue.Type))) { - context.RegisterOperationAction(AnalyzeArgument(attributeType), OperationKind.Argument); + context.ReportDiagnostic(Diagnostic.Create(Descriptor, lambda.Syntax.GetLocation())); + + // var kind = DetectSimpleOperation(returnedValue, lambda); + // + // switch (kind) + // { + // case SimpleOperationKind.Certain: + // context.ReportDiagnostic(Diagnostic.Create(Descriptor, lambda.Syntax.GetLocation())); + // break; + // case SimpleOperationKind.Maybe: + // context.ReportDiagnostic(Diagnostic.Create(Descriptor, lambda.Syntax.GetLocation(), DiagnosticSeverity.Info, null, null)); + // break; + // } } } - private static Action AnalyzeArgument(INamedTypeSymbol attributeType) - => context - => - { - var operation = (IArgumentOperation)context.Operation; - if (operation.Parameter is { } parameter - && operation.Value is IDelegateCreationOperation { Target: IAnonymousFunctionOperation lambda } - && parameter.GetAttributes().Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, attributeType)) - && MatchBlockOperationWithSingleReturn(lambda.Body) is { } returnedValue - && IsCheapOperation(returnedValue)) - { - context.ReportDiagnostic(Diagnostic.Create(Descriptor, lambda.Syntax.GetLocation())); - } - }; - private static IOperation? MatchBlockOperationWithSingleReturn(IOperation operation) - => operation is IBlockOperation block - && block.Operations.Length == 1 - && block.Operations[0] is IReturnOperation @return - ? @return.ReturnedValue - : null; + => operation is IBlockOperation blockOperation + && blockOperation.Operations.Length == 1 + && blockOperation.Operations[0] is IReturnOperation @return + ? @return.ReturnedValue + : null; - private static bool IsCheapOperation(IOperation operation) + private static SimpleOperationKind DetectSimpleOperation(IOperation operation, IAnonymousFunctionOperation lambda) => operation switch { - IParameterReferenceOperation or ILocalReferenceOperation or ILiteralOperation => true, - IConversionOperation conversion => IsCheapOperation(conversion.Operand), - IObjectCreationOperation objectCreation => objectCreation.Children.All(IsCheapOperation), - IFieldReferenceOperation fieldReference => fieldReference.Field.IsStatic, - IPropertyReferenceOperation propertyReference => propertyReference.Property.IsStatic, - _ => operation.ConstantValue.HasValue, + _ when operation.ConstantValue.HasValue => SimpleOperationKind.Certain, + IParameterReferenceOperation parameterReference when !SymbolEqualityComparer.Default.Equals(parameterReference.Parameter.ContainingSymbol, lambda.Symbol) => SimpleOperationKind.Certain, + ILocalReferenceOperation or ILiteralOperation => SimpleOperationKind.Certain, + IConversionOperation conversion => DetectSimpleOperation(conversion.Operand, lambda), + IObjectCreationOperation objectCreation when objectCreation.Children.Any() => Min(objectCreation.Children.Min(c => DetectSimpleOperation(c, lambda)), SimpleOperationKind.Maybe), + IObjectCreationOperation => SimpleOperationKind.Maybe, + IAnonymousObjectCreationOperation creation when creation.Initializers.Any() => creation.Initializers.Cast().Select(x => x.Value).Min(c => DetectSimpleOperation(c, lambda)), + IAnonymousObjectCreationOperation => SimpleOperationKind.Certain, + IFieldReferenceOperation fieldReference when fieldReference.Field.IsStatic => SimpleOperationKind.Certain, + IPropertyReferenceOperation propertyReference when propertyReference.Property.IsStatic => SimpleOperationKind.Maybe, + _ => SimpleOperationKind.None, }; + + private static SimpleOperationKind Min(SimpleOperationKind lhs, SimpleOperationKind rhs) => lhs < rhs ? lhs : rhs; + +#pragma warning disable SA1201 + private enum SimpleOperationKind +#pragma warning restore SA1201 + { + None, + Maybe, + Certain, + } }