Evaluate every component of the TimeSpan factory methods and constructors - #1440
Merged
Merged
Conversation
…tors TimeSpanOperation recognized the TimeSpan.FromXXX methods by name and read only their first argument, and recognized the constructors by the number of their arguments, so every additional component was silently dropped: TimeSpan.FromSeconds(1, 500) evaluated to 1000 ms, and MA0110 generated [GeneratedRegex(..., matchTimeoutMilliseconds: 1000)] for a 1500 ms timeout. It also truncated the durations shorter than a millisecond to 0, so MA0132 and MA0133 considered new TimeSpan(1) to be TimeSpan.Zero. The evaluation now computes ticks and takes the unit of a component from the name of its parameter, so the optional components, the additional components and the named arguments given in any order are all evaluated, and an overload with a parameter that does not name a known unit is unknown instead of being partially evaluated. The arithmetic is checked, so a duration that no TimeSpan can hold is unknown too, and a duration that is not an exact number of milliseconds is unknown as the callers replace it with a number of milliseconds. MA0110 also rejects the timeouts longer than Int32.MaxValue milliseconds: matchTimeoutMilliseconds is an Int32, and the code fixer used to drop both the TimeSpan argument and the attribute argument when the value did not fit.
This was referenced Sep 8, 2026
Merged
Closed
Closed
Closed
Closed
Bump Meziantou.Analyzer from 3.0.139 to 3.0.231
Analogy-LogViewer/Analogy.LogViewer.NLog.Targets#566
Closed
This was referenced Sep 17, 2026
Open
Open
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What was wrong
TimeSpanOperationrecognized theTimeSpan.FromXXXmethods by name and read onlyArguments[0], and recognized the constructors by the number of their arguments. Every additional component was silently dropped.The timeout is 1500 ms, but MA0110 recorded
RegexTimeout=1000and the code fixer generated:The fixed code compiles, but a workload that used to fit within the timeout can now time out.
The same helper backs MA0132 and MA0133, where the truncation to milliseconds produced a false positive:
new DateTimeOffset(DateTime.UnixEpoch, new TimeSpan(1))has a one tick offset, which was evaluated as0and reported asDateTimeOffset.UnixEpoch.What changed
TimeSpanOperationnow computes ticks and drives everything off parameter identity:days,hours,minutes,seconds,milliseconds,microseconds,ticks), so the optional components, the additional components and the named arguments given in any order are all evaluated. The overloads taking a single value name their parametervalue, so its unit comes from the method name. This also covers the microsecond constructor andTimeSpan.FromMicroseconds, which were previously unknown.checked, so a duration that noTimeSpancan hold, which the code creating it would reject at runtime, is unknown.GetMillisecondsreturns a value only when the duration is an exact number of milliseconds, as the callers replace the expression with a number of milliseconds.TimeSpan.MinValue,TimeSpan.MaxValueand the durations shorter than a millisecond are now unknown rather than truncated.UseRegexSourceGeneratorAnalyzer.IsConstantadditionally rejects the timeouts longer thanInt32.MaxValuemilliseconds. That closes a second silent drop:matchTimeoutMillisecondsis anInt32, and the fixer'sTryParseInt32returnednullfor a longer value, which removed theTimeSpanargument from the call and omitted the timeout from the generated attribute.Note for the reviewer
The
Timeout.Infinitecase of the field lookup is removed. It is aconst int, so it can never be aTimeSpantyped operation, and as a component it was folded into the constant path: the case was unreachable.Timeout.InfiniteTimeSpanandRegex.InfiniteMatchTimeoutare still evaluated as-1.MA0110 no longer reports a few
Regexconstructions it used to report, all of which produced a fix that changed the timeout or dropped it:TimeSpan.MinValue,TimeSpan.MaxValue, the durations shorter than a millisecond and the durations longer thanInt32.MaxValuemilliseconds. docs/Rules/MA0110.md documents this.Tests
Timeout_MultipleComponents, 8 cases:FromSeconds(1, 500), the named arguments given in another order,FromMilliseconds(1, 2000),FromMinutes(1, 30),FromHours(1, 2, 3, 4),FromDays(1, 2, 3, 4),new TimeSpan(seconds: 3, minutes: 2, hours: 1)and the microsecond constructor. It pins the .NET 9 reference assemblies, which is where those overloads were added.Timeout_NotRepresentableInMilliseconds_NoDiagnostic:new TimeSpan(1),FromMilliseconds(0.5),TimeSpan.MaxValueandFromDays(30).new TimeSpan(1)false positive andFromMinutes(0, 60).These are regression tests: reverting only
TimeSpanOperationwhile keeping them produces 11 failures.dotnet testpasses for the five Roslyn versions (20689 tests).dotnet run --project src/DocumentationGeneratorexits 0 with no further change.