Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable more Roslynator rules #21202

Merged
merged 3 commits into from Nov 19, 2023
Merged

Conversation

RoosterDragon
Copy link
Member

@RoosterDragon RoosterDragon commented Nov 10, 2023

Following on from #21192

Enforces 3 Roslynator rules across the project.

This is the last set of rules that I personally think might be worth enabling from Roslynator. However I think there's room for bikeshedding on these ones so I'm showing what the rules do out of the box, but I don't think we'll want to adjust this PR before taking any changes (if we even take any changes here).

See https://josefpihrt.github.io/docs/roslynator/analyzers for explanation of each rule.


RCS1077

array.FirstOrDefault(x => x.Foo);
// changes into
Array.Find(array, x => x.Foo);

list.FirstOrDefault(x => x.Foo);
// changes into
list.Find(x => x.Foo);

list.Select(x => x.Foo).ToList();
// changes into
list.ConvertAll(x => x.Foo);

enumerable.Select(x => x.Foo).Max();
// changes into
enumerable.Max(x => x.Foo);

// and a couple of other enumerable transforms

I don't mind the enumerable transforms the rule applies, but the array and list changes I find quite annoying. If I'm dealing with an input enumerable that happens to be a list or an array, the rule kicks in and wants me to stop using the LINQ enumerable methods and swap to something else. This is doubly annoying if I switch the input back into an enumerable and now everything breaks.

I'm tempted to add extensions method such as T FirstOrDefault<T>(this T[] array, Predicate<T>) => Array.Find(array, predicate); so that you can keep using the same methods without having to swap back and forth. However even doing that won't stop the rule suggesting the obtuse ConvertAll transformation for list-> list projections.

We could forget about this rule, or maybe we could manually fix the violations we care about, but not enforce the rule as a warning. It could be a lower severity or disabled entirely. We could add the extension method suggestion or go without.

I think some parts of this rule are useful, but I wouldn't want to enforce it straight up like this. Open to ideas.

RCS1146

nullable != null && nullable.Foo;
// changes into
nullable?.Foo;

nullable != null && nullable.SomeBool;
// changes into
nullable?.SomeBool == true;

nullable != null && !nullable.SomeBool;
// changes into
nullable?.SomeBool != true;

nullable == null || nullable.SomeBool;
// changes into
nullable?.SomeBool != false;

I like the first transformation, but I find the transformation for nullable Booleans deeply obtuse and would rather it kept it as a seperate null check. Again we could forget about the rule entirely, or manually apply some fixes but not all, or maybe enforce it at a lower severity, etc. Open to thoughts.

RCS1246

arrayOrList.First();
// changes into
arrayOrList[0];

Again, I think an extension method for First might be nicer here, to avoid having to change back and forth depending on the concrete type of your collection. Thoughts?

@pchote
Copy link
Member

pchote commented Nov 10, 2023

IMO these three rules don't bring any readability improvements, so whether we want them should hinge on their potential performance impact. Do you have an idea on what kind of impact these will have?

@PunkPun
Copy link
Member

PunkPun commented Nov 10, 2023

I haven't looked at the files changed but I'm a fan of RCS1246. I also like RCS107 but I suppose the devil's in the details there

as for RCS1146 IMO it only reduces readability and if it at all has a performance impact I really doubt its measurable. There's nothing faster than boolean math

@RoosterDragon
Copy link
Member Author

RCS1146 is purely a style choice.

Bench for RCS1077 and RCS1246:

public class BenchmarkJob
{
	readonly int[] array = Enumerable.Range(0, 1000).ToArray();
	readonly List<int> list = Enumerable.Range(0, 1000).ToList();

	[Benchmark]
	public int ArrayFind() => Array.Find(array, x => x == 500);

	[Benchmark]
	public int ArrayFirstOrDefault() => array.FirstOrDefault(x => x == 500);

	[Benchmark]
	public int ListFind() => list.Find(x => x == 500);

	[Benchmark]
	public int ListFirstOrDefault() => list.FirstOrDefault(x => x == 500);

	[Benchmark]
	public int ArrayFirst() => array.First();

	[Benchmark]
	public int ArrayIndexer() => array[0];

	[Benchmark]
	public int ListFirst() => list.First();

	[Benchmark]
	public int ListIndexer() => list[0];

	[Benchmark]
	public List<int> ListSelectToList() => list.Select(x => x * 2).ToList();

	[Benchmark]
	public List<int> ListConvertAll() => list.ConvertAll(x => x * 2);

	[Benchmark]
	public int SelectMax() => list.Select(x => x * 2).Max();

	[Benchmark]
	public int Max() => list.Max(x => x * 2);

	[Benchmark]
	public bool WhereAny() => list.Where(x => x % 2 == 0).Any(x => x > 500);

	[Benchmark]
	public bool Any() => list.Any(x => x % 2 == 0 && x > 500);
}
// * Summary *

BenchmarkDotNet v0.13.8, Windows 11 (10.0.22631.2428)
12th Gen Intel Core i7-12700H, 1 CPU, 20 logical and 14 physical cores
.NET SDK 7.0.403
  [Host]     : .NET 6.0.24 (6.0.2423.51814), X64 RyuJIT AVX2
  DefaultJob : .NET 6.0.24 (6.0.2423.51814), X64 RyuJIT AVX2


| Method              | Mean          | Error      | StdDev     |
|-------------------- |--------------:|-----------:|-----------:|
| ArrayFind           |   584.5566 ns | 11.3264 ns | 11.6314 ns |
| ArrayFirstOrDefault | 2,015.0439 ns | 33.5574 ns | 31.3896 ns |
| ListFind            |   596.8820 ns |  7.3363 ns |  6.8624 ns |
| ListFirstOrDefault  | 2,806.7147 ns | 26.6706 ns | 24.9477 ns |
| ArrayFirst          |    12.0498 ns |  0.1244 ns |  0.1164 ns |
| ArrayIndexer        |     0.1104 ns |  0.0185 ns |  0.0173 ns |
| ListFirst           |     7.5791 ns |  0.0490 ns |  0.0459 ns |
| ListIndexer         |     0.1073 ns |  0.0191 ns |  0.0179 ns |
| ListSelectToList    | 1,756.3014 ns | 13.6658 ns | 11.4115 ns |
| ListConvertAll      | 1,417.3055 ns | 20.5954 ns | 19.2649 ns |
| SelectMax           | 5,143.9698 ns | 30.1178 ns | 28.1722 ns |
| Max                 | 5,504.8629 ns | 75.3561 ns | 70.4881 ns |
| WhereAny            | 1,893.0500 ns | 19.2796 ns | 17.0909 ns |
| Any                 | 2,952.5293 ns | 27.1132 ns | 25.3617 ns |

// * Warnings *
MultimodalDistribution
  BenchmarkJob.ArrayIndexer: Default -> It seems that the distribution can have several modes (mValue = 2.89)

// * Hints *
Outliers
  BenchmarkJob.ArrayFind: Default        -> 1 outlier  was  removed (628.53 ns)
  BenchmarkJob.ListSelectToList: Default -> 2 outliers were removed, 3 outliers were detected (1.72 us, 1.78 us, 1.79 us)
  BenchmarkJob.WhereAny: Default         -> 1 outlier  was  removed (1.97 us)

// * Legends *
  Mean   : Arithmetic mean of all measurements
  Error  : Half of 99.9% confidence interval
  StdDev : Standard deviation of all measurements
  1 ns   : 1 Nanosecond (0.000000001 sec)

@RoosterDragon
Copy link
Member Author

Based on the benchmark and sentiments expressed thus far, I'm leaning towards the following approach:

  • RCS1077 - Introduce extension methods for Array.Find/List.Find and update code accordingly. Don't enforce the rule and don't make the other changes it suggests.
  • RCS1146 - Don't enforce this rule, make none of the suggested changes.
  • RCS1246 - Enforce this rule and make the suggested changes directly (no extension method).

That gets us the worthwile perf wins, whilst skipping everything else.

@penev92
Copy link
Member

penev92 commented Nov 16, 2023

Can we not enforce RCS1077 once we have the extension method(s)? I'd vote for enforcing it.
Also needs a rebase.

@PunkPun
Copy link
Member

PunkPun commented Nov 16, 2023

As VSCode let's me automatically fix RCS1077 my preference would be to enforce it. I'm also not a massive fan of custom extension methods as their discoverability if very low. As for RCS1146 I would definitely like to see that commit dropped

@RoosterDragon
Copy link
Member Author

Ok, changes pushed with the following flavour:

  • RCS1077 - Enforce this rule and make the suggested changes directly (no extension method).
  • RCS1146 - Don't enforce this rule, make none of the suggested changes.
  • RCS1246 - Enforce this rule and make the suggested changes directly (no extension method).

@penev92
Copy link
Member

penev92 commented Nov 17, 2023

Sorry, final verdict was FOR extension methods for RCS1077.
Also needs a rebase again.

This allows the LINQ spelling to be used, but benefits from the performance improvement of the specific methods for these classes that provide the same result.
@RoosterDragon
Copy link
Member Author

Rebased and added a separate commit for extension methods for RCS1077.

Copy link
Member

@PunkPun PunkPun left a comment

Choose a reason for hiding this comment

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

LGTM

@PunkPun PunkPun merged commit e6914f7 into OpenRA:bleed Nov 19, 2023
3 checks passed
@PunkPun
Copy link
Member

PunkPun commented Nov 19, 2023

Changelog

@RoosterDragon RoosterDragon deleted the style-ros-hard branch November 21, 2023 16:45
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.

None yet

4 participants