Skip to content
Merged
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
2 changes: 1 addition & 1 deletion eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
<!--
TODO: Remove pinned version once arcade supplies a compiler that enables the repo to compile.
-->
<MicrosoftNetCompilersToolsetVersion>4.4.0-1.22328.22</MicrosoftNetCompilersToolsetVersion>
<MicrosoftNetCompilersToolsetVersion>4.4.0-1.22356.12</MicrosoftNetCompilersToolsetVersion>
<!-- SDK dependencies -->
<MicrosoftDotNetCompatibilityVersion>2.0.0-preview.4.22252.4</MicrosoftDotNetCompatibilityVersion>
<!-- Arcade dependencies -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ namespace System.Text.RegularExpressions.Generator
public partial class RegexGenerator
{
/// <summary>Emits the definition of the partial method. This method just delegates to the property cache on the generated Regex-derived type.</summary>
private static void EmitRegexPartialMethod(RegexMethod regexMethod, IndentedTextWriter writer, string generatedClassName)
private static void EmitRegexPartialMethod(RegexMethod regexMethod, IndentedTextWriter writer)
{
// Emit the namespace.
RegexType? parent = regexMethod.DeclaringType;
Expand Down Expand Up @@ -59,7 +59,7 @@ private static void EmitRegexPartialMethod(RegexMethod regexMethod, IndentedText
writer.WriteLine("/// </code>");
writer.WriteLine("/// </remarks>");
writer.WriteLine($"[global::System.CodeDom.Compiler.{s_generatedCodeAttribute}]");
writer.WriteLine($"{regexMethod.Modifiers} global::System.Text.RegularExpressions.Regex {regexMethod.MethodName}() => global::{GeneratedNamespace}.{generatedClassName}.{regexMethod.GeneratedName}.Instance;");
writer.WriteLine($"{regexMethod.Modifiers} global::System.Text.RegularExpressions.Regex {regexMethod.MethodName}() => global::{GeneratedNamespace}.{regexMethod.GeneratedName}.Instance;");

// Unwind all scopes
while (writer.Indent != 0)
Expand All @@ -75,7 +75,8 @@ private static void EmitRegexLimitedBoilerplate(
{
writer.WriteLine($"/// <summary>Caches a <see cref=\"Regex\"/> instance for the {rm.MethodName} method.</summary>");
writer.WriteLine($"/// <remarks>A custom Regex-derived type could not be generated because {reason}.</remarks>");
writer.WriteLine($"internal sealed class {rm.GeneratedName} : Regex");
writer.WriteLine($"[{s_generatedCodeAttribute}]");
writer.WriteLine($"file sealed class {rm.GeneratedName} : Regex");
writer.WriteLine($"{{");
writer.WriteLine($" /// <summary>Cached, thread-safe singleton instance.</summary>");
writer.Write($" internal static readonly Regex Instance = ");
Expand All @@ -94,10 +95,15 @@ private static void EmitRegexLimitedBoilerplate(

/// <summary>Emits the Regex-derived type for a method whose RunnerFactory implementation was generated into <paramref name="runnerFactoryImplementation"/>.</summary>
private static void EmitRegexDerivedImplementation(
IndentedTextWriter writer, RegexMethod rm, string runnerFactoryImplementation)
IndentedTextWriter writer, RegexMethod rm, string runnerFactoryImplementation, bool allowUnsafe)
{
writer.WriteLine($"/// <summary>Custom <see cref=\"Regex\"/>-derived type for the {rm.MethodName} method.</summary>");
writer.WriteLine($"internal sealed class {rm.GeneratedName} : Regex");
writer.WriteLine($"[{s_generatedCodeAttribute}]");
if (allowUnsafe)
{
writer.WriteLine($"[SkipLocalsInit]");
}
writer.WriteLine($"file sealed class {rm.GeneratedName} : Regex");
writer.WriteLine($"{{");
writer.WriteLine($" /// <summary>Cached, thread-safe singleton instance.</summary>");
writer.WriteLine($" internal static readonly {rm.GeneratedName} Instance = new();");
Expand Down
38 changes: 10 additions & 28 deletions src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Tracing;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.DotnetRuntime.Extensions;

[assembly: System.Resources.NeutralResourcesLanguage("en-us")]
Expand Down Expand Up @@ -79,10 +74,10 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
Dictionary<string, string[]> requiredHelpers = new();
var sw = new StringWriter();
var writer = new IndentedTextWriter(sw);
writer.Indent += 3;
writer.Indent += 2;
writer.WriteLine();
EmitRegexDerivedTypeRunnerFactory(writer, regexMethod, requiredHelpers);
writer.Indent -= 3;
writer.Indent -= 2;
return (regexMethod, sw.ToString(), requiredHelpers);
})
.Collect();
Expand Down Expand Up @@ -138,7 +133,6 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
// used to name them. The boilerplate code generation that happens here is minimal when compared to
// the work required to generate the actual matching code for the regex.
int id = 0;
string generatedClassName = $"__{ComputeStringHash(compilationDataAndResults.Right.AssemblyName ?? ""):x}";

// To minimize generated code in the event of duplicated regexes, we only emit one derived Regex type per unique
// expression/options/timeout. A Dictionary<(expression, options, timeout), RegexMethod> is used to deduplicate, where the value of the
Expand Down Expand Up @@ -186,17 +180,13 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
emittedExpressions.Add(key, regexMethod);
}

EmitRegexPartialMethod(regexMethod, writer, generatedClassName);
EmitRegexPartialMethod(regexMethod, writer);
writer.WriteLine();
}
}

// At this point we've emitted all the partial method definitions, but we still need to emit the actual regex-derived implementations.
// These are all emitted inside of our generated class.
// TODO https://github.com/dotnet/csharplang/issues/5529:
// When C# provides a mechanism for shielding generated code from the rest of the project, it should be employed
// here for the generated class. At that point, the generated class wrapper can be removed, and all of the types
// generated inside of it (one for each regex as well as the helpers type) should be shielded.

writer.WriteLine($"namespace {GeneratedNamespace}");
writer.WriteLine($"{{");
Expand All @@ -213,17 +203,9 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
writer.WriteLine($" using System.Text.RegularExpressions;");
writer.WriteLine($" using System.Threading;");
writer.WriteLine($"");
if (compilationDataAndResults.Right.AllowUnsafe)
{
writer.WriteLine($" [SkipLocalsInit]");
}
writer.WriteLine($" [{s_generatedCodeAttribute}]");
writer.WriteLine($" [EditorBrowsable(EditorBrowsableState.Never)]");
writer.WriteLine($" internal static class {generatedClassName}");
writer.WriteLine($" {{");

// Emit each Regex-derived type.
writer.Indent += 2;
writer.Indent++;
foreach (object? result in results)
{
if (result is ValueTuple<RegexMethod, string, Diagnostic> limitedSupportResult)
Expand All @@ -238,19 +220,20 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
{
if (!regexImpl.Item1.IsDuplicate)
{
EmitRegexDerivedImplementation(writer, regexImpl.Item1, regexImpl.Item2);
EmitRegexDerivedImplementation(writer, regexImpl.Item1, regexImpl.Item2, compilationDataAndResults.Right.AllowUnsafe);
writer.WriteLine();
}
}
}
writer.Indent -= 2;
writer.Indent--;

// If any of the Regex-derived types asked for helper methods, emit those now.
if (requiredHelpers.Count != 0)
{
writer.Indent += 2;
writer.Indent++;
writer.WriteLine($"/// <summary>Helper methods used by generated <see cref=\"Regex\"/>-derived implementations.</summary>");
writer.WriteLine($"private static class {HelpersTypeName}");
writer.WriteLine($"[{s_generatedCodeAttribute}]");
writer.WriteLine($"file static class {HelpersTypeName}");
writer.WriteLine($"{{");
writer.Indent++;
bool sawFirst = false;
Expand All @@ -269,10 +252,9 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
}
writer.Indent--;
writer.WriteLine($"}}");
writer.Indent -= 2;
writer.Indent--;
}

writer.WriteLine($" }}");
writer.WriteLine($"}}");

// Save out the source
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IncludeRemoteExecutor>true</IncludeRemoteExecutor>
<!-- xUnit2008 is about regexes and isn't appropriate in the test project for regexes -->
Expand All @@ -8,6 +8,10 @@
<DebuggerSupport Condition="'$(DebuggerSupport)' == '' and '$(TargetOS)' == 'Browser'">true</DebuggerSupport>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<IsHighAotMemoryUsageTest>true</IsHighAotMemoryUsageTest> <!-- to avoid OOMs with source generation in wasm: https://github.com/dotnet/runtime/pull/60701 -->

<!-- Remove once the repo moves to a sufficiently high-enough version for file-scoped types -->
<MicrosoftCodeAnalysisVersion>4.4.0-1.22356.23</MicrosoftCodeAnalysisVersion>

</PropertyGroup>
<ItemGroup>
<Compile Include="AttRegexTests.cs" />
Expand Down