Skip to content

Tighten MSTEST0065 to fire on argument types, not only the generic T (#8766)#8770

Open
Evangelink wants to merge 2 commits into
mainfrom
dev/amauryleve/tighten-mstest0065-arg-types
Open

Tighten MSTEST0065 to fire on argument types, not only the generic T (#8766)#8770
Evangelink wants to merge 2 commits into
mainfrom
dev/amauryleve/tighten-mstest0065-arg-types

Conversation

@Evangelink
Copy link
Copy Markdown
Member

Fixes #8766.

Summary

Tighten MSTEST0065 (AvoidAssertAreEqualOnCollectionsAnalyzer) so it fires based on the argument / parameter type at the call site, not only on the generic type argument T. The analyzer previously inspected only targetMethod.TypeArguments[0] and so silently missed real-world pit-of-failure patterns where the caller forces T to a non-collection type.

Now flagged (in addition to today's cases):

csharp int[] arr1 = [1, 2]; int[] arr2 = [1, 2]; Assert.AreEqual<object>(arr1, arr2); // T = object, but arguments are int[] Assert.AreEqual((object)arr1, (object)arr2); // explicit object cast

The analyzer now walks back through implicit conversions on the expected/notExpected and actual arguments via IArgumentOperation.Value.WalkDownConversion() and fires the diagnostic if either argument's un-converted static type implements IEnumerable<T> (and is not string).

Behavior preserved

  • Null-literal short-circuit on either expected/notExpected or actual.
  • string exclusion.
  • The display type in the diagnostic message is the historical generic T when it is itself a collection; otherwise it is the un-converted argument type that triggered the report.
  • MSTEST0017 (argument-order analyzer) is untouched.

What is not covered

The acceptance criteria included object x = ...; Assert.AreEqual(x, y) where x is declared as object but assigned a collection. Without dataflow analysis the analyzer cannot see beyond the local's compile-time type object, so that case is intentionally not flagged (an explicit negative test documents this). The mixed case Assert.AreEqual(list, (object)list2) is flagged because at least one argument has a collection static type at the call site.

Risk / migration

This is a tightening of a Warning-severity analyzer and will produce new warnings on existing codebases that hit the newly-covered patterns. That is the desired effect (more pit-of-failure cases caught). Per the issue, this should ship separately from any severity escalation of MSTEST0065.

Tests

Added 7 new tests covering both positive and negative cases:

  • Assert.AreEqual<object>(arr1, arr2) — flagged
  • Assert.AreNotEqual<object>(arr1, arr2) — flagged
  • Assert.AreEqual((object)expected, (object)actual) — flagged
  • Assert.AreEqual(list, objectThatIsList) — flagged (one side has collection static type)
  • Assert.AreEqual<object>(s1, s2) with string arguments — not flagged
  • Assert.AreEqual(objectX, objectY) with both sides declared as object — not flagged (documents dataflow limitation)
  • Assert.AreEqual(record1, record2) for value-equal record — not flagged
  • Assert.AreEqual<object?>(null, actual) — null-literal short-circuit preserved across the new path

All 31 AvoidAssertAreEqualOnCollectionsAnalyzerTests pass, as do the 123 tests across the related UseProperAssertMethodsAnalyzer / AvoidAssertAreSameWithValueTypesAnalyzer / CollectionAssertToAssertMigrationAnalyzer suites.

Copilot AI review requested due to automatic review settings June 2, 2026 15:29
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR tightens MSTEST0065 (AvoidAssertAreEqualOnCollectionsAnalyzer) so it can report when callers widen the generic type argument (e.g., T=object) but still pass collection-typed arguments at the call site, and adds unit tests for the newly-covered scenarios. It also updates the test-improver agentic workflow guidance with a repo-specific constraint around analyzer test coverage.

Changes:

  • Update MSTEST0065 to fall back to inspecting un-converted argument static types (expected/notExpected + actual) when T itself is not a collection.
  • Add new unit tests covering explicit <object> generic arguments, object casts, mixed object/collection static types, and preserved exclusions (string + null-literal short-circuit).
  • Document a repository-specific constraint for the test-improver workflow (no VB.NET analyzer tests).
Show a summary per file
File Description
src/Analyzers/MSTest.Analyzers/AvoidAssertAreEqualOnCollectionsAnalyzer.cs Adjusts MSTEST0065 reporting logic to consider argument static types (after stripping conversions) when T isn’t itself a collection.
test/UnitTests/MSTest.Analyzers.UnitTests/AvoidAssertAreEqualOnCollectionsAnalyzerTests.cs Adds coverage for newly-detected call-site widening/casting patterns and key negative cases.
.github/workflows/test-improver.md Adds repo-specific constraints for the test-improver agent workflow (notably: no VB analyzer tests).

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 2

Comment thread .github/workflows/test-improver.md Outdated
Evangelink and others added 2 commits June 2, 2026 17:43
The AvoidAssertAreEqualOnCollectionsAnalyzer previously only inspected`targetMethod.TypeArguments[0]` (the inferred `T`) and so silently missed real-world pit-of-failure patterns where the caller forces `T` to a non-collection type, e.g.`Assert.AreEqual<object>(arr1, arr2)` or `Assert.AreEqual((object)arr, (object)arr)`.

The analyzer now also walks back through implicit conversions on the`expected`/`notExpected` and `actual` arguments and fires the diagnostic when the un-converted static type of either argument implements`IEnumerable<T>` (and is not `string`). The existing null-literal short-circuit and `string` exclusion are preserved.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ions aren't unwrapped

Per review feedback, switch from WalkDownConversion to WalkDownBuiltInConversion in GetCollectionArgumentType. The intent is to reason about the call-site static type after only built-in conversions (boxing, reference widening, implicit numeric conversions); a user-defined conversion changes what the user actually wrote, so the type after that conversion is the comparison the user intended.

Add a regression test that defines a user-defined implicit conversion from int[] to a non-collection Wrapper type and asserts MSTEST0065 stays quiet.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Evangelink Evangelink force-pushed the dev/amauryleve/tighten-mstest0065-arg-types branch from e84dc30 to c6ac2db Compare June 2, 2026 15:48
@Evangelink Evangelink enabled auto-merge (squash) June 2, 2026 16:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tighten MSTEST0065 to fire on argument types, not only the generic T

2 participants