Skip to content

Fix MA0185 false-positive for nullable value types#995

Merged
meziantou merged 4 commits intomainfrom
copilot/fix-false-positive-ma0185
Feb 3, 2026
Merged

Fix MA0185 false-positive for nullable value types#995
meziantou merged 4 commits intomainfrom
copilot/fix-false-positive-ma0185

Conversation

Copy link
Contributor

Copilot AI commented Feb 3, 2026

Fix MA0185 False-positive with int?

  • Analyze the issue and understand the root cause
  • Add test case reproducing the false-positive with int?
  • Fix the analyzer correctly by using UnwrapNullableOfT option
  • Verify the fix with tests (13 MA0185 tests pass)
  • Run broader tests to ensure no regressions (122 culture-sensitive tests, 76 MA0011 tests pass)
  • Update documentation (no changes needed)
  • Run code review and security checks

Root Cause

The MA0185 analyzer was using CultureSensitiveOptions.None which doesn't unwrap nullable types. This caused int? to be checked as Nullable<int> which doesn't implement IFormattable, leading to the false positive.

Changes Made

  • Modified SimplifyStringCreateWhenAllParametersAreCultureInvariantAnalyzer to use CultureSensitiveOptions.UnwrapNullableOfT instead of CultureSensitiveOptions.None
  • This ensures nullable types like int? are checked by their underlying type (int) which correctly identifies them as culture-sensitive
  • Reverted the change to CultureSensitiveFormattingContext.IsFormattableType() which was breaking MA0011's consider_nullable_types configuration option
  • Added test cases for nullable types:
    • StringCreateWithInvariantCulture_WithNullableInteger_NoDiagnostic - verifies fix for int?
    • StringCreateWithInvariantCulture_WithNullableDouble_NoDiagnostic - verifies fix for double?

Testing

  • All 13 MA0185 analyzer-specific tests pass
  • All 76 MA0011 analyzer tests pass (including the previously failing NullableInt32ToStringWithoutCultureInfo_DisabledConfig)
  • All 122 culture-sensitive related tests pass

Why This Approach

The previous approach of always unwrapping in IsFormattableType broke MA0011's behavior when consider_nullable_types is false. The correct fix is to make MA0185 explicitly unwrap nullable types by using the UnwrapNullableOfT option, which respects the semantics of checking underlying types for string.Create with InvariantCulture.

Original prompt

This section details on the original issue you should resolve

<issue_title>MA0185 False-positive with int?</issue_title>
<issue_description>### Version of the Meziantou.Analyzer NuGet package

2.0.295

Rule Identifier

MA0185

Target Framework

net10.0

C# Language version

C# 14

Description

I'm getting false positive error

0>Program.cs(10,14): Error MA0185 : Simplify string.Create when all parameters are culture invariant (https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0185.md)

Changing to int n (non-nullable) doesn't trigger the rule.

If I change to interpolated string, I'm getting a different warning

MA0076 - Do not use implicit culture-sensitive ToString in interpolated strings

Reproduction Steps

Minimal code:

class Program
{
	static void Main(string[] args)
	{
		int? n = 42;
		var s = string.Create(CultureInfo.InvariantCulture, $@"/v{n}");
		Console.WriteLine(s);
	}
}

Other information

No response</issue_description>

<agent_instructions>It seems MA0185 consider the expression to be culture invariant, when it is not</agent_instructions>

Comments on the Issue (you are @copilot in this section)


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI and others added 2 commits February 3, 2026 15:02
Co-authored-by: meziantou <509220+meziantou@users.noreply.github.com>
Co-authored-by: meziantou <509220+meziantou@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix false-positive with MA0185 for int? Fix MA0185 false-positive for nullable value types Feb 3, 2026
Copilot AI requested a review from meziantou February 3, 2026 15:09
@meziantou
Copy link
Owner

@copilot Test fails

[xUnit.net 00:01:30.36] Meziantou.Analyzer.Test.Rules.UseIFormatProviderAnalyzerTests.NullableInt32ToStringWithoutCultureInfo_DisabledConfig [FAIL]
[xUnit.net 00:01:30.37] Mismatch between number of diagnostics returned, expected "0" actual "1"
[xUnit.net 00:01:30.37]
[xUnit.net 00:01:30.37] Diagnostics:
[xUnit.net 00:01:30.37] // Test0.cs(1,1): warning MA0011: Use an overload of 'ToString' that has a 'System.IFormatProvider' parameter
[xUnit.net 00:01:30.37] GetCSharpResultAt(1, 1, List1.MA0011) [xUnit.net 00:01:30.37] [xUnit.net 00:01:30.37] [xUnit.net 00:01:30.37] Source: [xUnit.net 00:01:30.37] ((int?)1).ToString();^^^^^^^^^^^^^^^^^^^^ MA0011: Use an overload of 'ToString' that has a 'System.IFormatProvider' parameter [xUnit.net 00:01:30.37] [xUnit.net 00:01:30.37] Stack Trace: [xUnit.net 00:01:30.37] /_/tests/Meziantou.Analyzer.Test/Helpers/ProjectBuilder.Validation.cs(79,0): at TestHelper.ProjectBuilder.VerifyDiagnosticResults(IEnumerable1 actualResults, IList1 analyzers, IList1 expectedResults)
[xUnit.net 00:01:30.37] //tests/Meziantou.Analyzer.Test/Helpers/ProjectBuilder.Validation.cs(53,0): at TestHelper.ProjectBuilder.VerifyDiagnostics(IList1 analyzers, IList1 expected)
[xUnit.net 00:01:30.37] /
/tests/Meziantou.Analyzer.Test/Helpers/ProjectBuilder.Validation.cs(35,0): at TestHelper.ProjectBuilder.ValidateAsync()
[xUnit.net 00:01:30.37] /_/tests/Meziantou.Analyzer.Test/Rules/UseIFormatProviderAnalyzerTests.cs(242,0): at Meziantou.Analyzer.Test.Rules.UseIFormatProviderAnalyzerTests.NullableInt32ToStringWithoutCultureInfo_DisabledConfig()
[xUnit.net 00:01:30.37] --- End of stack trace from previous location ---
Passed Meziantou.Analyzer.Test.Rules.DoNotUseZeroToInitializeAnEnumValueTests.EnumBaseType_NonZero(baseType: "uint", value: "(int)1") [83 ms]
Passed Meziantou.Analyzer.Test.Rules.UseIFormatProviderAnalyzerTests.Int32ToStringWithoutCultureInfo_ShouldReportDiagnostic [124 ms]
Passed Meziantou.Analyzer.Test.Rules.AvoidUnusedInternalTypesAnalyzerTests.InternalClassUsedInTypeGenericConstraint_NoDiagnostic [85 ms]
Passed Meziantou.Analyzer.Test.Rules.DoNotUseZeroToInitializeAnEnumValueTests.EnumBaseType_NonZero(baseType: "uint", value: "1L") [97 ms]
Passed Meziantou.Analyzer.Test.Rules.AvoidUnusedInternalTypesAnalyzerTests.UnusedInternalDelegate_Diagnostic [93 ms]
Failed Meziantou.Analyzer.Test.Rules.UseIFormatProviderAnalyzerTests.NullableInt32ToStringWithoutCultureInfo_DisabledConfig [127 ms]
Error Message:
Mismatch between number of diagnostics returned, expected "0" actual "1"

Diagnostics:
// Test0.cs(1,1): warning MA0011: Use an overload of 'ToString' that has a 'System.IFormatProvider' parameter
GetCSharpResultAt(1, 1, List`1.MA0011)

Source:
((int?)1).ToString();^^^^^^^^^^^^^^^^^^^^ MA0011: Use an overload of 'ToString' that has a 'System.IFormatProvider' parameter

Stack Trace:
at TestHelper.ProjectBuilder.VerifyDiagnosticResults(IEnumerable1 actualResults, IList1 analyzers, IList1 expectedResults) in /_/tests/Meziantou.Analyzer.Test/Helpers/ProjectBuilder.Validation.cs:line 79 at TestHelper.ProjectBuilder.VerifyDiagnostics(IList1 analyzers, IList`1 expected) in //tests/Meziantou.Analyzer.Test/Helpers/ProjectBuilder.Validation.cs:line 53
at TestHelper.ProjectBuilder.ValidateAsync() in /
/tests/Meziantou.Analyzer.Test/Helpers/ProjectBuilder.Validation.cs:line 35
at Meziantou.Analyzer.Test.Rules.UseIFormatProviderAnalyzerTests.NullableInt32ToStringWithoutCultureInfo_DisabledConfig() in /_/tests/Meziantou.Analyzer.Test/Rules/UseIFormatProviderAnalyzerTests.cs:line 242
--- End of stack trace from previous location ---

Error: Mismatch between number of diagnostics returned, expected "0" actual "1"

…rmattableType

Co-authored-by: meziantou <509220+meziantou@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 3, 2026

@copilot Test fails

[xUnit.net 00:01:30.36] Meziantou.Analyzer.Test.Rules.UseIFormatProviderAnalyzerTests.NullableInt32ToStringWithoutCultureInfo_DisabledConfig [FAIL]
...

Fixed in e2c59fe. The issue was that I modified IsFormattableType to always unwrap nullable types, which broke MA0011's consider_nullable_types=false configuration. The correct fix is to make MA0185 use CultureSensitiveOptions.UnwrapNullableOfT instead of None, which respects the semantic that for string.Create with InvariantCulture we should check the underlying type of nullable values. All tests now pass.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • 1s1vsblobprodcus386.vsblob.vsassets.io
    • Triggering command: /usr/share/dotnet/dotnet dotnet restore --no-dependencies /home/REDACTED/work/Meziantou.Analyzer/Meziantou.Analyzer/tests/Meziantou.Analyzer.Test/Meziantou.Analyzer.Test.csproj --packages /home/REDACTED/work/Meziantou.Analyzer/.codeql-scratch/dbs/csharp/working/packages /p:DisableImplicitNuGetFallbackFolder=true --verbosity normal /p:TargetFrameworkRootPath=/home/REDACTED/work/Meziantou.Analyzer/.codeql-scratch/dbs/csharp/working/emptyFakeDotnetRoot /p:NetCoreTargetingPackRoot=/home/REDACTED/work/Meziantou.Analyzer/.codeql-scratch/dbs/csharp/working/emptyFakeDotnetRoot /p:AllowMissingPrunePackageData=true (dns block)
  • st8vsblobprodcus339.vsblob.vsassets.io
    • Triggering command: /usr/share/dotnet/dotnet dotnet restore --no-dependencies /home/REDACTED/work/Meziantou.Analyzer/Meziantou.Analyzer/Meziantou.Analyzer.slnx --packages /home/REDACTED/work/Meziantou.Analyzer/.codeql-scratch/dbs/csharp/working/packages /p:DisableImplicitNuGetFallbackFolder=true --verbosity normal /p:TargetFrameworkRootPath=/home/REDACTED/work/Meziantou.Analyzer/.codeql-scratch/dbs/csharp/working/emptyFakeDotnetRoot /p:NetCoreTargetingPackRoot=/home/REDACTED/work/Meziantou.Analyzer/.codeql-scratch/dbs/csharp/working/emptyFakeDotnetRoot /p:AllowMissingPrunePackageData=true (dns block)
    • Triggering command: /usr/share/dotnet/dotnet dotnet restore --no-dependencies /home/REDACTED/work/Meziantou.Analyzer/Meziantou.Analyzer/tests/Meziantou.Analyzer.Test/Meziantou.Analyzer.Test.csproj --packages /home/REDACTED/work/Meziantou.Analyzer/.codeql-scratch/dbs/csharp/working/packages /p:DisableImplicitNuGetFallbackFolder=true --verbosity normal /p:TargetFrameworkRootPath=/home/REDACTED/work/Meziantou.Analyzer/.codeql-scratch/dbs/csharp/working/emptyFakeDotnetRoot /p:NetCoreTargetingPackRoot=/home/REDACTED/work/Meziantou.Analyzer/.codeql-scratch/dbs/csharp/working/emptyFakeDotnetRoot /p:AllowMissingPrunePackageData=true (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

This was referenced Feb 4, 2026
IhateTrains pushed a commit to ParadoxGameConverters/ImperatorToCK3 that referenced this pull request Feb 5, 2026
Updated
[Meziantou.Analyzer](https://github.com/meziantou/Meziantou.Analyzer)
from 2.0.285 to 2.0.297.

<details>
<summary>Release notes</summary>

_Sourced from [Meziantou.Analyzer's
releases](https://github.com/meziantou/Meziantou.Analyzer/releases)._

## 2.0.297

NuGet package:
<https://www.nuget.org/packages/Meziantou.Analyzer/2.0.297>

## What's Changed
* MA0007: Support multiline pattern-matching expressions by @​Copilot in
meziantou/Meziantou.Analyzer#997


**Full Changelog**:
meziantou/Meziantou.Analyzer@2.0.296...2.0.297

## 2.0.296

NuGet package:
<https://www.nuget.org/packages/Meziantou.Analyzer/2.0.296>

## What's Changed
* Fix MA0185 false-positive for nullable value types by @​Copilot in
meziantou/Meziantou.Analyzer#995


**Full Changelog**:
meziantou/Meziantou.Analyzer@2.0.295...2.0.296

## 2.0.295

NuGet package:
<https://www.nuget.org/packages/Meziantou.Analyzer/2.0.295>

## What's Changed
* Update default TargetFramework and adjust test setups by @​meziantou
in meziantou/Meziantou.Analyzer#993


**Full Changelog**:
meziantou/Meziantou.Analyzer@2.0.294...2.0.295

## 2.0.294

NuGet package:
<https://www.nuget.org/packages/Meziantou.Analyzer/2.0.294>

**Full Changelog**:
meziantou/Meziantou.Analyzer@2.0.293...2.0.294

## 2.0.293

NuGet package:
<https://www.nuget.org/packages/Meziantou.Analyzer/2.0.293>

## What's Changed
* Fix MA0183 false-positive with params array arguments in string.Format
by @​Copilot in meziantou/Meziantou.Analyzer#991


**Full Changelog**:
meziantou/Meziantou.Analyzer@2.0.292...2.0.293

## 2.0.292

NuGet package:
<https://www.nuget.org/packages/Meziantou.Analyzer/2.0.292>

## What's Changed
* Enable MA0184 by default with silent severity by @​Copilot in
meziantou/Meziantou.Analyzer#988


**Full Changelog**:
meziantou/Meziantou.Analyzer@2.0.291...2.0.292

## 2.0.291

NuGet package:
<https://www.nuget.org/packages/Meziantou.Analyzer/2.0.291>

## What's Changed
* MA0185: Add tests and docs for literal-only interpolated strings by
@​Copilot in meziantou/Meziantou.Analyzer#987


**Full Changelog**:
meziantou/Meziantou.Analyzer@2.0.290...2.0.291

## 2.0.290

NuGet package:
<https://www.nuget.org/packages/Meziantou.Analyzer/2.0.290>

## What's Changed
* Add MA0185: Simplify string.Create with culture-invariant parameters
by @​Copilot in meziantou/Meziantou.Analyzer#986


**Full Changelog**:
meziantou/Meziantou.Analyzer@2.0.289...2.0.290

## 2.0.289

NuGet package:
<https://www.nuget.org/packages/Meziantou.Analyzer/2.0.289>

## What's Changed
* Add MA0184: Report interpolated strings without parameters by
@​Copilot in meziantou/Meziantou.Analyzer#984


**Full Changelog**:
meziantou/Meziantou.Analyzer@2.0.288...2.0.289

## 2.0.288

NuGet package:
<https://www.nuget.org/packages/Meziantou.Analyzer/2.0.288>

## What's Changed
* Disable MA0042 async overload suggestions for TemporaryDirectory in
test projects by @​Copilot in
meziantou/Meziantou.Analyzer#980


**Full Changelog**:
meziantou/Meziantou.Analyzer@2.0.287...2.0.288

## 2.0.287

NuGet package:
<https://www.nuget.org/packages/Meziantou.Analyzer/2.0.287>

## What's Changed
* Add MA0183: Detect string.Format without placeholders or formatting
arguments by @​Copilot in
meziantou/Meziantou.Analyzer#979


**Full Changelog**:
meziantou/Meziantou.Analyzer@2.0.286...2.0.287

## 2.0.286

NuGet package:
<https://www.nuget.org/packages/Meziantou.Analyzer/2.0.286>

**Full Changelog**:
meziantou/Meziantou.Analyzer@2.0.285...2.0.286

Commits viewable in [compare
view](meziantou/Meziantou.Analyzer@2.0.285...2.0.297).
</details>

[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=Meziantou.Analyzer&package-manager=nuget&previous-version=2.0.285&new-version=2.0.297)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
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.

MA0185 False-positive with int?

2 participants