未验证 提交 29ac1156 编写于 作者: S Stephen Toub 提交者: GitHub

Use file-scoped types in RegexGenerator output (#71765)

* Use file-scoped types in RegexGenerator output

* Update System.Text.RegularExpressions.Tests.csproj
上级 f00c4cb4
......@@ -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 -->
......
......@@ -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;
......@@ -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)
......@@ -75,7 +75,8 @@ private static void EmitRegexPartialMethod(RegexMethod regexMethod, IndentedText
{
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 = ");
......@@ -94,10 +95,15 @@ private static void EmitRegexPartialMethod(RegexMethod regexMethod, IndentedText
/// <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();");
......
// 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")]
......@@ -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();
......@@ -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
......@@ -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($"{{");
......@@ -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)
......@@ -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;
......@@ -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
......
<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 -->
......@@ -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" />
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册