Skip to content

Commit 98961f8

Browse files
authored
Merge pull request #816 from Sergio0694/dev/improved-unsafe-blocks-analyzer
Make 'AllowsUnsafeBlocks' analyzer only trigger on attribute uses
2 parents ebee50d + 2a9f622 commit 98961f8

File tree

8 files changed

+106
-22
lines changed

8 files changed

+106
-22
lines changed

src/ComputeSharp.D2D1.SourceGenerators/Diagnostics/Analyzers/MissingAllowUnsafeBlocksCompilationOptionAnalyzer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public sealed class MissingAllowUnsafeBlocksCompilationOptionAnalyzer : MissingA
1313
/// Creates a new <see cref="MissingAllowUnsafeBlocksCompilationOptionAnalyzer"/> instance.
1414
/// </summary>
1515
public MissingAllowUnsafeBlocksCompilationOptionAnalyzer()
16-
: base(MissingAllowUnsafeBlocksOption)
16+
: base(MissingAllowUnsafeBlocksOption, "ComputeSharp.D2D1.D2DGeneratedPixelShaderDescriptorAttribute")
1717
{
1818
}
1919
}

src/ComputeSharp.D2D1.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -944,19 +944,18 @@ partial class DiagnosticDescriptors
944944
/// <summary>
945945
/// Gets a <see cref="DiagnosticDescriptor"/> for when the <c>AllowUnsafeBlocks</c> option is not set.
946946
/// <para>
947-
/// Format: <c>"Unsafe blocks must be enabled for the source generators to emit valid code (add &lt;AllowUnsafeBlocks&gt;true&lt;/AllowUnsafeBlocks&gt; to your .csproj/.props file)"</c>.
947+
/// Format: <c>"Using [D2DGeneratedPixelShaderDescriptor] requires unsafe blocks being enabled, as they needed by the source generators to emit valid code (add &lt;AllowUnsafeBlocks&gt;true&lt;/AllowUnsafeBlocks&gt; to your .csproj/.props file)"</c>.
948948
/// </para>
949949
/// </summary>
950950
public static readonly DiagnosticDescriptor MissingAllowUnsafeBlocksOption = new(
951951
id: "CMPSD2D0064",
952952
title: "Missing 'AllowUnsafeBlocks' compilation option",
953-
messageFormat: "Unsafe blocks must be enabled for the source generators to emit valid code (add <AllowUnsafeBlocks>true</AllowUnsafeBlocks> to your .csproj/.props file)",
953+
messageFormat: "Using [D2DGeneratedPixelShaderDescriptor] requires unsafe blocks being enabled, as they needed by the source generators to emit valid code (add <AllowUnsafeBlocks>true</AllowUnsafeBlocks> to your .csproj/.props file)",
954954
category: "ComputeSharp.D2D1.Shaders",
955955
defaultSeverity: DiagnosticSeverity.Error,
956956
isEnabledByDefault: true,
957-
description: "Unsafe blocks must be enabled for the source generators to emit valid code (the <AllowUnsafeBlocks>true</AllowUnsafeBlocks> option must be set in the .csproj/.props file).",
958-
helpLinkUri: "https://github.com/Sergio0694/ComputeSharp",
959-
customTags: WellKnownDiagnosticTags.CompilationEnd);
957+
description: "Unsafe blocks must be enabled when using [D2DGeneratedPixelShaderDescriptor] for the source generators to emit valid code (the <AllowUnsafeBlocks>true</AllowUnsafeBlocks> option must be set in the .csproj/.props file).",
958+
helpLinkUri: "https://github.com/Sergio0694/ComputeSharp");
960959

961960
/// <summary>
962961
/// Gets a <see cref="DiagnosticDescriptor"/> for when a pixel shader type doesn't have an associated descriptor.

src/ComputeSharp.SourceGeneration.Hlsl/Diagnostics/Analyzers/MissingAllowUnsafeBlocksCompilationOptionAnalyzerBase.cs

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ namespace ComputeSharp.SourceGeneration.Diagnostics;
99
/// A diagnostic analyzer that generates an error if the <c>AllowUnsafeBlocks</c> compilation option is not set.
1010
/// </summary>
1111
/// <param name="diagnosticDescriptor">The <see cref="DiagnosticDescriptor"/> instance to use.</param>
12-
public abstract class MissingAllowUnsafeBlocksCompilationOptionAnalyzerBase(DiagnosticDescriptor diagnosticDescriptor) : DiagnosticAnalyzer
12+
/// <param name="generatedShaderDescriptorFullyQualifiedTypeName">The fully qualified type name of the target attribute.</param>
13+
public abstract class MissingAllowUnsafeBlocksCompilationOptionAnalyzerBase(
14+
DiagnosticDescriptor diagnosticDescriptor,
15+
string generatedShaderDescriptorFullyQualifiedTypeName) : DiagnosticAnalyzer
1316
{
1417
/// <inheritdoc/>
1518
public sealed override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = [diagnosticDescriptor];
@@ -20,13 +23,38 @@ public sealed override void Initialize(AnalysisContext context)
2023
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
2124
context.EnableConcurrentExecution();
2225

23-
context.RegisterCompilationAction(context =>
26+
context.RegisterCompilationStartAction(context =>
2427
{
25-
// Check whether unsafe blocks are available, and emit an error if they are not
26-
if (!context.Compilation.IsAllowUnsafeBlocksEnabled())
28+
// If unsafe blocks are allowed, we'll never need to emit a diagnostic
29+
if (context.Compilation.IsAllowUnsafeBlocksEnabled())
2730
{
28-
context.ReportDiagnostic(Diagnostic.Create(diagnosticDescriptor, location: null));
31+
return;
2932
}
33+
34+
// Get the symbol for the target attribute type
35+
if (context.Compilation.GetTypeByMetadataName(generatedShaderDescriptorFullyQualifiedTypeName) is not { } generatedShaderDescriptorAttributeSymbol)
36+
{
37+
return;
38+
}
39+
40+
// Check if any types in the compilation are using the trigger attribute
41+
context.RegisterSymbolAction(context =>
42+
{
43+
// Only struct types are possible targets
44+
if (context.Symbol is not INamedTypeSymbol { TypeKind: TypeKind.Struct } typeSymbol)
45+
{
46+
return;
47+
}
48+
49+
// If the target type is not using the attribute, there's nothing left to do
50+
if (!typeSymbol.TryGetAttributeWithType(generatedShaderDescriptorAttributeSymbol, out AttributeData? attribute))
51+
{
52+
return;
53+
}
54+
55+
// Emit the error on the attribute use
56+
context.ReportDiagnostic(Diagnostic.Create(diagnosticDescriptor, attribute.GetLocation()));
57+
}, SymbolKind.NamedType);
3058
});
3159
}
3260
}

src/ComputeSharp.SourceGenerators/Diagnostics/Analyzers/MissingAllowUnsafeBlocksCompilationOptionAnalyzer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public sealed class MissingAllowUnsafeBlocksCompilationOptionAnalyzer : MissingA
1313
/// Creates a new <see cref="MissingAllowUnsafeBlocksCompilationOptionAnalyzer"/> instance.
1414
/// </summary>
1515
public MissingAllowUnsafeBlocksCompilationOptionAnalyzer()
16-
: base(MissingAllowUnsafeBlocksOption)
16+
: base(MissingAllowUnsafeBlocksOption, "ComputeSharp.GeneratedComputeShaderDescriptorAttribute")
1717
{
1818
}
1919
}

src/ComputeSharp.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -720,19 +720,18 @@ partial class DiagnosticDescriptors
720720
/// <summary>
721721
/// Gets a <see cref="DiagnosticDescriptor"/> for when the <c>AllowUnsafeBlocks</c> option is not set.
722722
/// <para>
723-
/// Format: <c>"Unsafe blocks must be enabled for the source generators to emit valid code (add &lt;AllowUnsafeBlocks&gt;true&lt;/AllowUnsafeBlocks&gt; to your .csproj/.props file)"</c>.
723+
/// Format: <c>"Using [GeneratedComputeShaderDescriptor] requires unsafe blocks being enabled, as they needed by the source generators to emit valid code (add &lt;AllowUnsafeBlocks&gt;true&lt;/AllowUnsafeBlocks&gt; to your .csproj/.props file)"</c>.
724724
/// </para>
725725
/// </summary>
726726
public static readonly DiagnosticDescriptor MissingAllowUnsafeBlocksOption = new(
727727
id: "CMPS0052",
728728
title: "Missing 'AllowUnsafeBlocks' compilation option",
729-
messageFormat: "Unsafe blocks must be enabled for the source generators to emit valid code (add <AllowUnsafeBlocks>true</AllowUnsafeBlocks> to your .csproj/.props file)",
729+
messageFormat: "Using [GeneratedComputeShaderDescriptor] requires unsafe blocks being enabled, as they needed by the source generators to emit valid code (add <AllowUnsafeBlocks>true</AllowUnsafeBlocks> to your .csproj/.props file)",
730730
category: "ComputeSharp.Shaders",
731731
defaultSeverity: DiagnosticSeverity.Error,
732732
isEnabledByDefault: true,
733-
description: "Unsafe blocks must be enabled for the source generators to emit valid code (the <AllowUnsafeBlocks>true</AllowUnsafeBlocks> option must be set in the .csproj/.props file).",
734-
helpLinkUri: "https://github.com/Sergio0694/ComputeSharp",
735-
customTags: WellKnownDiagnosticTags.CompilationEnd);
733+
description: "Unsafe blocks must be enabled when using [GeneratedComputeShaderDescriptor] for the source generators to emit valid code (the <AllowUnsafeBlocks>true</AllowUnsafeBlocks> option must be set in the .csproj/.props file).",
734+
helpLinkUri: "https://github.com/Sergio0694/ComputeSharp");
736735

737736
/// <summary>
738737
/// Gets a <see cref="DiagnosticDescriptor"/> for when a compute shader type doesn't have an associated descriptor.

tests/ComputeSharp.D2D1.Tests.SourceGenerators/Test_D2DPixelShaderDescriptorGenerator_Analyzers.cs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,27 @@ namespace ComputeSharp.D2D1.Tests.SourceGenerators;
99
public class Test_D2DPixelShaderDescriptorGenerator_Analyzers
1010
{
1111
[TestMethod]
12-
public async Task MissingD2DPixelShaderDescriptor_ComputeShader()
12+
public async Task AllowsUnsafeBlocksNotEnabled_Warns()
13+
{
14+
const string source = """
15+
using ComputeSharp;
16+
using ComputeSharp.D2D1;
17+
18+
[{|CMPSD2D0064:D2DGeneratedPixelShaderDescriptor|}]
19+
internal partial struct MyShader : ID2D1PixelShader
20+
{
21+
public Float4 Execute()
22+
{
23+
return 0;
24+
}
25+
}
26+
""";
27+
28+
await CSharpAnalyzerWithLanguageVersionTest<MissingAllowUnsafeBlocksCompilationOptionAnalyzer>.VerifyAnalyzerAsync(source, allowUnsafeBlocks: false);
29+
}
30+
31+
[TestMethod]
32+
public async Task MissingD2DPixelShaderDescriptor_Warns()
1333
{
1434
const string source = """
1535
using ComputeSharp;
@@ -28,7 +48,7 @@ public Float4 Execute()
2848
}
2949

3050
[TestMethod]
31-
public async Task MissingComputeShaderDescriptor_ManuallyImplemented_DoesNotWarn()
51+
public async Task MissingD2DPixelShaderDescriptor_ManuallyImplemented_DoesNotWarn()
3252
{
3353
const string source = """
3454
using System;

tests/ComputeSharp.Tests.SourceGenerators/Helpers/CSharpAnalyzerWithLanguageVersionTest{TAnalyzer}.cs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ namespace ComputeSharp.Tests.SourceGenerators.Helpers;
2323
internal sealed class CSharpAnalyzerWithLanguageVersionTest<TAnalyzer> : CSharpAnalyzerTest<TAnalyzer, MSTestVerifier>
2424
where TAnalyzer : DiagnosticAnalyzer, new()
2525
{
26+
/// <summary>
27+
/// Whether to enable unsafe blocks.
28+
/// </summary>
29+
private readonly bool allowUnsafeBlocks;
30+
2631
/// <summary>
2732
/// The C# language version to use to parse code.
2833
/// </summary>
@@ -31,12 +36,20 @@ internal sealed class CSharpAnalyzerWithLanguageVersionTest<TAnalyzer> : CSharpA
3136
/// <summary>
3237
/// Creates a new <see cref="CSharpAnalyzerWithLanguageVersionTest{TAnalyzer}"/> instance with the specified paramaters.
3338
/// </summary>
39+
/// <param name="allowUnsafeBlocks">Whether to enable unsafe blocks.</param>
3440
/// <param name="languageVersion">The C# language version to use to parse code.</param>
35-
private CSharpAnalyzerWithLanguageVersionTest(LanguageVersion languageVersion)
41+
private CSharpAnalyzerWithLanguageVersionTest(bool allowUnsafeBlocks, LanguageVersion languageVersion)
3642
{
43+
this.allowUnsafeBlocks = allowUnsafeBlocks;
3744
this.languageVersion = languageVersion;
3845
}
3946

47+
/// <inheritdoc/>
48+
protected override CompilationOptions CreateCompilationOptions()
49+
{
50+
return new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: this.allowUnsafeBlocks);
51+
}
52+
4053
/// <inheritdoc/>
4154
protected override ParseOptions CreateParseOptions()
4255
{
@@ -45,10 +58,14 @@ protected override ParseOptions CreateParseOptions()
4558

4659
/// <inheritdoc cref="AnalyzerVerifier{TAnalyzer, TTest, TVerifier}.VerifyAnalyzerAsync"/>
4760
/// <param name="source">The source code to analyze.</param>
61+
/// <param name="allowUnsafeBlocks">Whether to enable unsafe blocks.</param>
4862
/// <param name="languageVersion">The language version to use to run the test.</param>
49-
public static Task VerifyAnalyzerAsync(string source, LanguageVersion languageVersion = LanguageVersion.CSharp12)
63+
public static Task VerifyAnalyzerAsync(
64+
string source,
65+
bool allowUnsafeBlocks = true,
66+
LanguageVersion languageVersion = LanguageVersion.CSharp12)
5067
{
51-
CSharpAnalyzerWithLanguageVersionTest<TAnalyzer> test = new(languageVersion) { TestCode = source };
68+
CSharpAnalyzerWithLanguageVersionTest<TAnalyzer> test = new(allowUnsafeBlocks, languageVersion) { TestCode = source };
5269

5370
test.TestState.ReferenceAssemblies = ReferenceAssemblies.Net.Net80;
5471
test.TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(typeof(Core::ComputeSharp.Hlsl).Assembly.Location));

tests/ComputeSharp.Tests.SourceGenerators/Test_ComputeShaderDescriptorGenerator_Analyzers.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,27 @@ namespace ComputeSharp.Tests.SourceGenerators;
88
[TestClass]
99
public class Test_ComputeShaderDescriptorGenerator_Analyzers
1010
{
11+
[TestMethod]
12+
public async Task AllowsUnsafeBlocksNotEnabled_Warns()
13+
{
14+
const string source = """
15+
using ComputeSharp;
16+
17+
[ThreadGroupSize(DefaultThreadGroupSizes.X)]
18+
[{|CMPS0052:GeneratedComputeShaderDescriptor|}]
19+
internal readonly partial struct MyShader : IComputeShader
20+
{
21+
private readonly ReadWriteBuffer<float> buffer;
22+
23+
public void Execute()
24+
{
25+
}
26+
}
27+
""";
28+
29+
await CSharpAnalyzerWithLanguageVersionTest<MissingAllowUnsafeBlocksCompilationOptionAnalyzer>.VerifyAnalyzerAsync(source, allowUnsafeBlocks: false);
30+
}
31+
1132
[TestMethod]
1233
public async Task MissingComputeShaderDescriptor_ComputeShader()
1334
{

0 commit comments

Comments
 (0)