From 5229d4617dc1afb2be7a89f71761a41980192661 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 26 May 2026 03:56:46 +0000 Subject: [PATCH 1/3] Initial plan From 7821ce87bf9659ab5eb6723727e749a9cb726344 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 26 May 2026 04:04:12 +0000 Subject: [PATCH 2/3] Fix WTG3014/WTG3013 code-fix to add explicit type argument when element type differs from collection type When converting Concat to Prepend/Append, if the element being prepended/appended has a different type than the Concat method's type argument (e.g., new object[] { viewModel }.Concat(viewModel.Items)), the code fix now emits Prepend/Append with an explicit type argument to avoid CS0411. Agent-Logs-Url: https://github.com/WiseTechGlobal/WTG.Analyzers/sessions/f7bd2b00-ecf5-43fe-af98-395e79831d26 Co-authored-by: brian-reichle <18721383+brian-reichle@users.noreply.github.com> --- .../PrependTypeMismatch/Diagnostics.xml | 19 +++ .../PrependTypeMismatch/Result.cs | 20 ++++ .../PrependTypeMismatch/Source.cs | 20 ++++ .../LinqEnumerableCodeFixProvider.cs | 109 ++++++++++++++++-- 4 files changed, 160 insertions(+), 8 deletions(-) create mode 100644 src/WTG.Analyzers.Test/TestData/LinqEnumerableAnalyzer/PrependTypeMismatch/Diagnostics.xml create mode 100644 src/WTG.Analyzers.Test/TestData/LinqEnumerableAnalyzer/PrependTypeMismatch/Result.cs create mode 100644 src/WTG.Analyzers.Test/TestData/LinqEnumerableAnalyzer/PrependTypeMismatch/Source.cs diff --git a/src/WTG.Analyzers.Test/TestData/LinqEnumerableAnalyzer/PrependTypeMismatch/Diagnostics.xml b/src/WTG.Analyzers.Test/TestData/LinqEnumerableAnalyzer/PrependTypeMismatch/Diagnostics.xml new file mode 100644 index 0000000..1d46f3c --- /dev/null +++ b/src/WTG.Analyzers.Test/TestData/LinqEnumerableAnalyzer/PrependTypeMismatch/Diagnostics.xml @@ -0,0 +1,19 @@ + + + + + Test0.cs: (15, 3-36) + + + Test0.cs: (16, 3-20) + + + + + Test0.cs: (17, 3-25) + + + Test0.cs: (18, 3-20) + + + diff --git a/src/WTG.Analyzers.Test/TestData/LinqEnumerableAnalyzer/PrependTypeMismatch/Result.cs b/src/WTG.Analyzers.Test/TestData/LinqEnumerableAnalyzer/PrependTypeMismatch/Result.cs new file mode 100644 index 0000000..a169c14 --- /dev/null +++ b/src/WTG.Analyzers.Test/TestData/LinqEnumerableAnalyzer/PrependTypeMismatch/Result.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System.Linq; + +public class ViewModel +{ + public IEnumerable Items { get; set; } +} + +public class Bob +{ + public void Method() + { + var viewModel = new ViewModel(); + + viewModel.Items.Prepend(viewModel); + Enumerable.Prepend(viewModel.Items, viewModel); + viewModel.Items.Append(viewModel); + Enumerable.Append(viewModel.Items, viewModel); + } +} diff --git a/src/WTG.Analyzers.Test/TestData/LinqEnumerableAnalyzer/PrependTypeMismatch/Source.cs b/src/WTG.Analyzers.Test/TestData/LinqEnumerableAnalyzer/PrependTypeMismatch/Source.cs new file mode 100644 index 0000000..6f9a9b8 --- /dev/null +++ b/src/WTG.Analyzers.Test/TestData/LinqEnumerableAnalyzer/PrependTypeMismatch/Source.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System.Linq; + +public class ViewModel +{ + public IEnumerable Items { get; set; } +} + +public class Bob +{ + public void Method() + { + var viewModel = new ViewModel(); + + new object[] { viewModel }.Concat(viewModel.Items); + Enumerable.Concat(new object[] { viewModel }, viewModel.Items); + viewModel.Items.Concat(new object[] { viewModel }); + Enumerable.Concat(viewModel.Items, new object[] { viewModel }); + } +} diff --git a/src/WTG.Analyzers/Analyzers/LinqEnumerable/LinqEnumerableCodeFixProvider.cs b/src/WTG.Analyzers/Analyzers/LinqEnumerable/LinqEnumerableCodeFixProvider.cs index 18ea77f..375813f 100644 --- a/src/WTG.Analyzers/Analyzers/LinqEnumerable/LinqEnumerableCodeFixProvider.cs +++ b/src/WTG.Analyzers/Analyzers/LinqEnumerable/LinqEnumerableCodeFixProvider.cs @@ -67,6 +67,7 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context) public static async Task ReplaceWithAppropriateMethod(Document document, Diagnostic diagnostic, CancellationToken c) { var root = await document.RequireSyntaxRootAsync(c).ConfigureAwait(true); + var semanticModel = await document.RequireSemanticModelAsync(c).ConfigureAwait(true); var memberAccessExpression = (MemberAccessExpressionSyntax)root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true); @@ -75,7 +76,7 @@ public static async Task ReplaceWithAppropriateMethod(Document documen return document; } - var newNode = FixMemberAccessExpression(memberAccessExpression, diagnostic); + var newNode = FixMemberAccessExpression(memberAccessExpression, diagnostic, semanticModel); if (newNode == null) { @@ -86,18 +87,18 @@ public static async Task ReplaceWithAppropriateMethod(Document documen memberAccessExpression.Parent, newNode)); } - public static SyntaxNode? FixMemberAccessExpression(MemberAccessExpressionSyntax m, Diagnostic d) + public static SyntaxNode? FixMemberAccessExpression(MemberAccessExpressionSyntax m, Diagnostic d, SemanticModel semanticModel) { return d.Id switch { - Rules.DontUseConcatWhenAppendingSingleElementToEnumerablesDiagnosticID => FixConcatWithAppendMethod(m), - Rules.DontUseConcatWhenPrependingSingleElementToEnumerablesDiagnosticID => FixConcatWithPrependMethod(m), + Rules.DontUseConcatWhenAppendingSingleElementToEnumerablesDiagnosticID => FixConcatWithAppendMethod(m, semanticModel), + Rules.DontUseConcatWhenPrependingSingleElementToEnumerablesDiagnosticID => FixConcatWithPrependMethod(m, semanticModel), Rules.DontConcatTwoCollectionsDefinedWithLiteralsDiagnosticID => FixConcatWithNewCollection(m), _ => null, }; } - public static SyntaxNode FixConcatWithAppendMethod(MemberAccessExpressionSyntax m) + public static SyntaxNode FixConcatWithAppendMethod(MemberAccessExpressionSyntax m, SemanticModel semanticModel) { var invocation = (InvocationExpressionSyntax?)m.Parent; NRT.Assert(invocation != null, "MemberAccessExpression should have a parent."); @@ -125,7 +126,7 @@ public static SyntaxNode FixConcatWithAppendMethod(MemberAccessExpressionSyntax .WithTriviaFrom(m.Expression) .WithAdditionalAnnotations(Simplifier.Annotation), m.OperatorToken, - IdentifierName(nameof(Enumerable.Append)) + GetMethodName(nameof(Enumerable.Append), invocation, semanticModel) .WithTriviaFrom(m.Name))) .WithArgumentList( ArgumentList( @@ -133,7 +134,7 @@ public static SyntaxNode FixConcatWithAppendMethod(MemberAccessExpressionSyntax .WithTriviaFrom(invocation); } - public static SyntaxNode? FixConcatWithPrependMethod(MemberAccessExpressionSyntax m) + public static SyntaxNode? FixConcatWithPrependMethod(MemberAccessExpressionSyntax m, SemanticModel semanticModel) { var invocation = (InvocationExpressionSyntax?)m.Parent; NRT.Assert(invocation != null, "MemberAccessExpression should have a parent."); @@ -166,7 +167,7 @@ public static SyntaxNode FixConcatWithAppendMethod(MemberAccessExpressionSyntax SyntaxKind.SimpleMemberAccessExpression, member, m.OperatorToken, - IdentifierName(nameof(Enumerable.Prepend)) + GetMethodName(nameof(Enumerable.Prepend), invocation, semanticModel) .WithTriviaFrom(m.Name))) .WithArgumentList( ArgumentList( @@ -213,5 +214,97 @@ public static SyntaxNode FixConcatWithNewCollection(MemberAccessExpressionSyntax .WithTriviaFrom(invocation) .WithAdditionalAnnotations(Simplifier.Annotation); } + + static SimpleNameSyntax GetMethodName(string methodName, InvocationExpressionSyntax invocation, SemanticModel semanticModel) + { + if (NeedsExplicitTypeArgument(invocation, semanticModel, out var typeArgument)) + { + return GenericName(Identifier(methodName)) + .WithTypeArgumentList( + TypeArgumentList( + SingletonSeparatedList(typeArgument))); + } + + return IdentifierName(methodName); + } + + static bool NeedsExplicitTypeArgument(InvocationExpressionSyntax invocation, SemanticModel semanticModel, out TypeSyntax typeArgument) + { + var methodSymbol = semanticModel.GetSymbolInfo(invocation).Symbol as IMethodSymbol; + + if (methodSymbol != null && methodSymbol.TypeArguments.Length == 1) + { + var concatTypeArg = methodSymbol.TypeArguments[0]; + var elementExpression = GetElementExpression(invocation); + + if (elementExpression != null) + { + var elementType = semanticModel.GetTypeInfo(elementExpression).Type; + + if (elementType != null && !SymbolEqualityComparer.Default.Equals(elementType, concatTypeArg)) + { + typeArgument = ParseTypeName(concatTypeArg.ToMinimalDisplayString(semanticModel, invocation.SpanStart)); + return true; + } + } + } + + typeArgument = null!; + return false; + } + + static ExpressionSyntax? GetElementExpression(InvocationExpressionSyntax invocation) + { + var memberAccess = invocation.Expression as MemberAccessExpressionSyntax; + + if (memberAccess == null) + { + return null; + } + + var arguments = invocation.ArgumentList.Arguments; + + if (arguments.Count == 1) + { + // Extension method style: collection.Concat(enumerable) or enumerable.Concat(collection) + // For Prepend: new T[] { element }.Concat(enumerable) - element is in m.Expression + // For Append: enumerable.Concat(new T[] { element }) - element is in arguments[0] + var receiverExpr = memberAccess.Expression.TryGetExpressionFromParenthesizedExpression(); + var argExpr = arguments[0].Expression.TryGetExpressionFromParenthesizedExpression(); + + // Check which one is the single-element collection + var receiverFirstValue = LinqEnumerableUtils.GetFirstValue(receiverExpr); + if (receiverFirstValue != null) + { + return receiverFirstValue; + } + + var argFirstValue = LinqEnumerableUtils.GetFirstValue(argExpr); + if (argFirstValue != null) + { + return argFirstValue; + } + } + else if (arguments.Count == 2) + { + // Static method style: Enumerable.Concat(collection, enumerable) + var arg0Expr = arguments[0].Expression.TryGetExpressionFromParenthesizedExpression(); + var arg1Expr = arguments[1].Expression.TryGetExpressionFromParenthesizedExpression(); + + var arg0FirstValue = LinqEnumerableUtils.GetFirstValue(arg0Expr); + if (arg0FirstValue != null) + { + return arg0FirstValue; + } + + var arg1FirstValue = LinqEnumerableUtils.GetFirstValue(arg1Expr); + if (arg1FirstValue != null) + { + return arg1FirstValue; + } + } + + return null; + } } } From ba72978c7779b8345db233902b38832fbcb75bae Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 26 May 2026 05:03:22 +0000 Subject: [PATCH 3/3] Replace SemanticModel usage with syntactic TypeSyntax extraction and move ViewModel after Bob Address review feedback: - Extract type argument from array/object creation syntax instead of using expensive SemanticModel. Uses Simplifier.Annotation to let Roslyn remove redundant type arguments. - Move ViewModel class after Bob in test files to reduce future diagnostic location churn. Agent-Logs-Url: https://github.com/WiseTechGlobal/WTG.Analyzers/sessions/6e17af6a-5f2e-43d6-9651-5ddffe01a303 Co-authored-by: brian-reichle <18721383+brian-reichle@users.noreply.github.com> --- .../PrependTypeMismatch/Diagnostics.xml | 8 +- .../PrependTypeMismatch/Result.cs | 10 +- .../PrependTypeMismatch/Source.cs | 10 +- .../LinqEnumerableCodeFixProvider.cs | 119 ++++++------------ 4 files changed, 49 insertions(+), 98 deletions(-) diff --git a/src/WTG.Analyzers.Test/TestData/LinqEnumerableAnalyzer/PrependTypeMismatch/Diagnostics.xml b/src/WTG.Analyzers.Test/TestData/LinqEnumerableAnalyzer/PrependTypeMismatch/Diagnostics.xml index 1d46f3c..efb060e 100644 --- a/src/WTG.Analyzers.Test/TestData/LinqEnumerableAnalyzer/PrependTypeMismatch/Diagnostics.xml +++ b/src/WTG.Analyzers.Test/TestData/LinqEnumerableAnalyzer/PrependTypeMismatch/Diagnostics.xml @@ -2,18 +2,18 @@ - Test0.cs: (15, 3-36) + Test0.cs: (10, 3-36) - Test0.cs: (16, 3-20) + Test0.cs: (11, 3-20) - Test0.cs: (17, 3-25) + Test0.cs: (12, 3-25) - Test0.cs: (18, 3-20) + Test0.cs: (13, 3-20) diff --git a/src/WTG.Analyzers.Test/TestData/LinqEnumerableAnalyzer/PrependTypeMismatch/Result.cs b/src/WTG.Analyzers.Test/TestData/LinqEnumerableAnalyzer/PrependTypeMismatch/Result.cs index a169c14..de82624 100644 --- a/src/WTG.Analyzers.Test/TestData/LinqEnumerableAnalyzer/PrependTypeMismatch/Result.cs +++ b/src/WTG.Analyzers.Test/TestData/LinqEnumerableAnalyzer/PrependTypeMismatch/Result.cs @@ -1,11 +1,6 @@ using System.Collections.Generic; using System.Linq; -public class ViewModel -{ - public IEnumerable Items { get; set; } -} - public class Bob { public void Method() @@ -18,3 +13,8 @@ public void Method() Enumerable.Append(viewModel.Items, viewModel); } } + +public class ViewModel +{ + public IEnumerable Items { get; set; } +} diff --git a/src/WTG.Analyzers.Test/TestData/LinqEnumerableAnalyzer/PrependTypeMismatch/Source.cs b/src/WTG.Analyzers.Test/TestData/LinqEnumerableAnalyzer/PrependTypeMismatch/Source.cs index 6f9a9b8..bdc2318 100644 --- a/src/WTG.Analyzers.Test/TestData/LinqEnumerableAnalyzer/PrependTypeMismatch/Source.cs +++ b/src/WTG.Analyzers.Test/TestData/LinqEnumerableAnalyzer/PrependTypeMismatch/Source.cs @@ -1,11 +1,6 @@ using System.Collections.Generic; using System.Linq; -public class ViewModel -{ - public IEnumerable Items { get; set; } -} - public class Bob { public void Method() @@ -18,3 +13,8 @@ public void Method() Enumerable.Concat(viewModel.Items, new object[] { viewModel }); } } + +public class ViewModel +{ + public IEnumerable Items { get; set; } +} diff --git a/src/WTG.Analyzers/Analyzers/LinqEnumerable/LinqEnumerableCodeFixProvider.cs b/src/WTG.Analyzers/Analyzers/LinqEnumerable/LinqEnumerableCodeFixProvider.cs index 375813f..ef7e656 100644 --- a/src/WTG.Analyzers/Analyzers/LinqEnumerable/LinqEnumerableCodeFixProvider.cs +++ b/src/WTG.Analyzers/Analyzers/LinqEnumerable/LinqEnumerableCodeFixProvider.cs @@ -67,7 +67,6 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context) public static async Task ReplaceWithAppropriateMethod(Document document, Diagnostic diagnostic, CancellationToken c) { var root = await document.RequireSyntaxRootAsync(c).ConfigureAwait(true); - var semanticModel = await document.RequireSemanticModelAsync(c).ConfigureAwait(true); var memberAccessExpression = (MemberAccessExpressionSyntax)root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true); @@ -76,7 +75,7 @@ public static async Task ReplaceWithAppropriateMethod(Document documen return document; } - var newNode = FixMemberAccessExpression(memberAccessExpression, diagnostic, semanticModel); + var newNode = FixMemberAccessExpression(memberAccessExpression, diagnostic); if (newNode == null) { @@ -87,33 +86,37 @@ public static async Task ReplaceWithAppropriateMethod(Document documen memberAccessExpression.Parent, newNode)); } - public static SyntaxNode? FixMemberAccessExpression(MemberAccessExpressionSyntax m, Diagnostic d, SemanticModel semanticModel) + public static SyntaxNode? FixMemberAccessExpression(MemberAccessExpressionSyntax m, Diagnostic d) { return d.Id switch { - Rules.DontUseConcatWhenAppendingSingleElementToEnumerablesDiagnosticID => FixConcatWithAppendMethod(m, semanticModel), - Rules.DontUseConcatWhenPrependingSingleElementToEnumerablesDiagnosticID => FixConcatWithPrependMethod(m, semanticModel), + Rules.DontUseConcatWhenAppendingSingleElementToEnumerablesDiagnosticID => FixConcatWithAppendMethod(m), + Rules.DontUseConcatWhenPrependingSingleElementToEnumerablesDiagnosticID => FixConcatWithPrependMethod(m), Rules.DontConcatTwoCollectionsDefinedWithLiteralsDiagnosticID => FixConcatWithNewCollection(m), _ => null, }; } - public static SyntaxNode FixConcatWithAppendMethod(MemberAccessExpressionSyntax m, SemanticModel semanticModel) + public static SyntaxNode FixConcatWithAppendMethod(MemberAccessExpressionSyntax m) { var invocation = (InvocationExpressionSyntax?)m.Parent; NRT.Assert(invocation != null, "MemberAccessExpression should have a parent."); var listOfArgumentsAndSeparators = new List(); + ExpressionSyntax singleElementCollection; + switch (invocation.ArgumentList.Arguments.Count) { case 1: - listOfArgumentsAndSeparators.Add(Argument(LinqEnumerableUtils.GetFirstValue(invocation.ArgumentList.Arguments[0].Expression)!)); + singleElementCollection = invocation.ArgumentList.Arguments[0].Expression; + listOfArgumentsAndSeparators.Add(Argument(LinqEnumerableUtils.GetFirstValue(singleElementCollection)!)); break; case 2: + singleElementCollection = invocation.ArgumentList.Arguments[1].Expression; listOfArgumentsAndSeparators.Add(invocation.ArgumentList.Arguments[0]); listOfArgumentsAndSeparators.Add(Token(SyntaxKind.CommaToken)); - listOfArgumentsAndSeparators.Add(Argument(LinqEnumerableUtils.GetFirstValue(invocation.ArgumentList.Arguments[1].Expression)!)); + listOfArgumentsAndSeparators.Add(Argument(LinqEnumerableUtils.GetFirstValue(singleElementCollection)!)); break; default: throw new InvalidOperationException("Unreachable - Code fix should never trigger for >2 arguments."); @@ -126,7 +129,7 @@ public static SyntaxNode FixConcatWithAppendMethod(MemberAccessExpressionSyntax .WithTriviaFrom(m.Expression) .WithAdditionalAnnotations(Simplifier.Annotation), m.OperatorToken, - GetMethodName(nameof(Enumerable.Append), invocation, semanticModel) + GetMethodName(nameof(Enumerable.Append), singleElementCollection) .WithTriviaFrom(m.Name))) .WithArgumentList( ArgumentList( @@ -134,7 +137,7 @@ public static SyntaxNode FixConcatWithAppendMethod(MemberAccessExpressionSyntax .WithTriviaFrom(invocation); } - public static SyntaxNode? FixConcatWithPrependMethod(MemberAccessExpressionSyntax m, SemanticModel semanticModel) + public static SyntaxNode? FixConcatWithPrependMethod(MemberAccessExpressionSyntax m) { var invocation = (InvocationExpressionSyntax?)m.Parent; NRT.Assert(invocation != null, "MemberAccessExpression should have a parent."); @@ -142,19 +145,22 @@ public static SyntaxNode FixConcatWithAppendMethod(MemberAccessExpressionSyntax var listOfArgumentsAndSeparators = new List(); ExpressionSyntax member; + ExpressionSyntax singleElementCollection; switch (invocation.ArgumentList.Arguments.Count) { case 1: - listOfArgumentsAndSeparators.Add(Argument(LinqEnumerableUtils.GetFirstValue(m.Expression.TryGetExpressionFromParenthesizedExpression())!)); + singleElementCollection = m.Expression.TryGetExpressionFromParenthesizedExpression(); + listOfArgumentsAndSeparators.Add(Argument(LinqEnumerableUtils.GetFirstValue(singleElementCollection)!)); member = ParenthesizedExpression(invocation.ArgumentList.Arguments[0].Expression.WithoutTrivia()) .WithTriviaFrom(m.Expression) .WithAdditionalAnnotations(Simplifier.Annotation); break; case 2: + singleElementCollection = invocation.ArgumentList.Arguments[0].Expression; listOfArgumentsAndSeparators.Add(invocation.ArgumentList.Arguments[1]); listOfArgumentsAndSeparators.Add(Token(SyntaxKind.CommaToken)); - listOfArgumentsAndSeparators.Add(Argument(LinqEnumerableUtils.GetFirstValue(invocation.ArgumentList.Arguments[0].Expression)!)); + listOfArgumentsAndSeparators.Add(Argument(LinqEnumerableUtils.GetFirstValue(singleElementCollection)!)); member = m.Expression; break; @@ -167,7 +173,7 @@ public static SyntaxNode FixConcatWithAppendMethod(MemberAccessExpressionSyntax SyntaxKind.SimpleMemberAccessExpression, member, m.OperatorToken, - GetMethodName(nameof(Enumerable.Prepend), invocation, semanticModel) + GetMethodName(nameof(Enumerable.Prepend), singleElementCollection) .WithTriviaFrom(m.Name))) .WithArgumentList( ArgumentList( @@ -215,93 +221,38 @@ public static SyntaxNode FixConcatWithNewCollection(MemberAccessExpressionSyntax .WithAdditionalAnnotations(Simplifier.Annotation); } - static SimpleNameSyntax GetMethodName(string methodName, InvocationExpressionSyntax invocation, SemanticModel semanticModel) + static SimpleNameSyntax GetMethodName(string methodName, ExpressionSyntax singleElementCollection) { - if (NeedsExplicitTypeArgument(invocation, semanticModel, out var typeArgument)) + var elementType = GetCollectionElementType(singleElementCollection.TryGetExpressionFromParenthesizedExpression()); + + if (elementType != null) { return GenericName(Identifier(methodName)) .WithTypeArgumentList( TypeArgumentList( - SingletonSeparatedList(typeArgument))); + SingletonSeparatedList( + elementType.WithoutTrivia()))) + .WithAdditionalAnnotations(Simplifier.Annotation); } return IdentifierName(methodName); } - static bool NeedsExplicitTypeArgument(InvocationExpressionSyntax invocation, SemanticModel semanticModel, out TypeSyntax typeArgument) + static TypeSyntax? GetCollectionElementType(ExpressionSyntax expression) { - var methodSymbol = semanticModel.GetSymbolInfo(invocation).Symbol as IMethodSymbol; - - if (methodSymbol != null && methodSymbol.TypeArguments.Length == 1) + switch (expression.Kind()) { - var concatTypeArg = methodSymbol.TypeArguments[0]; - var elementExpression = GetElementExpression(invocation); + case SyntaxKind.ArrayCreationExpression: + return ((ArrayCreationExpressionSyntax)expression).Type.ElementType; - if (elementExpression != null) - { - var elementType = semanticModel.GetTypeInfo(elementExpression).Type; - - if (elementType != null && !SymbolEqualityComparer.Default.Equals(elementType, concatTypeArg)) + case SyntaxKind.ObjectCreationExpression: + var objectCreationType = ((ObjectCreationExpressionSyntax)expression).Type; + if (objectCreationType is GenericNameSyntax genericName && genericName.TypeArgumentList.Arguments.Count == 1) { - typeArgument = ParseTypeName(concatTypeArg.ToMinimalDisplayString(semanticModel, invocation.SpanStart)); - return true; + return genericName.TypeArgumentList.Arguments[0]; } - } - } - - typeArgument = null!; - return false; - } - - static ExpressionSyntax? GetElementExpression(InvocationExpressionSyntax invocation) - { - var memberAccess = invocation.Expression as MemberAccessExpressionSyntax; - - if (memberAccess == null) - { - return null; - } - - var arguments = invocation.ArgumentList.Arguments; - if (arguments.Count == 1) - { - // Extension method style: collection.Concat(enumerable) or enumerable.Concat(collection) - // For Prepend: new T[] { element }.Concat(enumerable) - element is in m.Expression - // For Append: enumerable.Concat(new T[] { element }) - element is in arguments[0] - var receiverExpr = memberAccess.Expression.TryGetExpressionFromParenthesizedExpression(); - var argExpr = arguments[0].Expression.TryGetExpressionFromParenthesizedExpression(); - - // Check which one is the single-element collection - var receiverFirstValue = LinqEnumerableUtils.GetFirstValue(receiverExpr); - if (receiverFirstValue != null) - { - return receiverFirstValue; - } - - var argFirstValue = LinqEnumerableUtils.GetFirstValue(argExpr); - if (argFirstValue != null) - { - return argFirstValue; - } - } - else if (arguments.Count == 2) - { - // Static method style: Enumerable.Concat(collection, enumerable) - var arg0Expr = arguments[0].Expression.TryGetExpressionFromParenthesizedExpression(); - var arg1Expr = arguments[1].Expression.TryGetExpressionFromParenthesizedExpression(); - - var arg0FirstValue = LinqEnumerableUtils.GetFirstValue(arg0Expr); - if (arg0FirstValue != null) - { - return arg0FirstValue; - } - - var arg1FirstValue = LinqEnumerableUtils.GetFirstValue(arg1Expr); - if (arg1FirstValue != null) - { - return arg1FirstValue; - } + break; } return null;