Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
Expand Down Expand Up @@ -78,10 +79,24 @@ public void Execute(GeneratorExecutionContext executionContext)

// Stage 3. Emit source code from the spec models.
OnSourceEmitting?.Invoke(contextGenerationSpecs.ToImmutableArray());
Emitter emitter = new(executionContext);
foreach (ContextGenerationSpec contextGenerationSpec in contextGenerationSpecs)

// Ensure the source generator emits number literals using invariant culture.
// This prevents issues such as locale-specific negative signs (e.g., U+2212 in fi-FI)
// from being written to generated source files.
// Note: RS1035 is already disabled at the file level for this Roslyn version.
CultureInfo originalCulture = CultureInfo.CurrentCulture;
Copy link
Member

Choose a reason for hiding this comment

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

@stephentoub this seems a bit heavy handed to me and I could see this regressing accidentally in the future. Shouldn't we instead fix the individual callsites hitting culture sensitive APIs?

Copy link
Member

Choose a reason for hiding this comment

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

Why does it seem heavy handed? It's making it so that everything is done culture-invariant, which is what we want. Is there ever a situation where we'd want something the generator to be culture-aware to the build machine that ran the generator? Assuming not, the only thing we'd get from doing it per call-site is greater risk of a bug tail and more expensive formatting.

CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
try
{
Emitter emitter = new(executionContext);
foreach (ContextGenerationSpec contextGenerationSpec in contextGenerationSpecs)
{
emitter.Emit(contextGenerationSpec);
}
}
finally
{
emitter.Emit(contextGenerationSpec);
CultureInfo.CurrentCulture = originalCulture;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Immutable;
using System.Globalization;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
Expand Down Expand Up @@ -69,8 +70,23 @@ private void ReportDiagnosticsAndEmitSource(SourceProductionContext sourceProduc
}

OnSourceEmitting?.Invoke(ImmutableArray.Create(input.ContextGenerationSpec));
Emitter emitter = new(sourceProductionContext);
emitter.Emit(input.ContextGenerationSpec);

// Ensure the source generator emits number literals using invariant culture.
// This prevents issues such as locale-specific negative signs (e.g., U+2212 in fi-FI)
// from being written to generated source files.
#pragma warning disable RS1035 // CultureInfo.CurrentCulture is banned in analyzers
CultureInfo originalCulture = CultureInfo.CurrentCulture;
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
try
{
Emitter emitter = new(sourceProductionContext);
emitter.Emit(input.ContextGenerationSpec);
}
finally
{
CultureInfo.CurrentCulture = originalCulture;
}
#pragma warning restore RS1035
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Linq;
using System.Tests;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Xunit;
Expand Down Expand Up @@ -965,5 +966,51 @@ public partial class MyContext : JsonSerializerContext
Assert.NotEmpty(result.NewCompilation.GetDiagnostics().Where(d => d.Id == "EXP001"));
}
#endif

[Fact]
public void NegativeJsonPropertyOrderGeneratesValidCode()
{
// Test for https://github.com/dotnet/runtime/issues/121277
// Verify that negative JsonPropertyOrder values generate compilable code
// even on locales that use non-ASCII minus signs (e.g., fi_FI uses U+2212)
string source = """
using System.Text.Json.Serialization;

namespace Test
{
public class MyClass
{
[JsonPropertyOrder(-1)]
public int FirstProperty { get; set; }

[JsonPropertyOrder(0)]
public int SecondProperty { get; set; }

[JsonPropertyOrder(-100)]
public int ThirdProperty { get; set; }
}

[JsonSerializable(typeof(MyClass))]
public partial class MyContext : JsonSerializerContext
{
}
}
""";

// Test with fi_FI culture which uses U+2212 minus sign for negative numbers
using (new ThreadCultureChange("fi-FI"))
{
Compilation compilation = CompilationHelper.CreateCompilation(source);
JsonSourceGeneratorResult result = CompilationHelper.RunJsonSourceGenerator(compilation, logger: logger);

// The generated code should compile without errors
// If the bug exists, we'd see CS1525, CS1002, CS1056, or CS0201 errors
var errors = result.NewCompilation.GetDiagnostics()
.Where(d => d.Severity == DiagnosticSeverity.Error)
.ToList();

Assert.Empty(errors);
}
}
}
}
Loading