diff --git a/src/ComputeSharp.D2D1.UI.SourceGenerators/CanvasEffectPropertyGenerator.Execute.cs b/src/ComputeSharp.D2D1.UI.SourceGenerators/CanvasEffectPropertyGenerator.Execute.cs index 169f8e974..1dcc44486 100644 --- a/src/ComputeSharp.D2D1.UI.SourceGenerators/CanvasEffectPropertyGenerator.Execute.cs +++ b/src/ComputeSharp.D2D1.UI.SourceGenerators/CanvasEffectPropertyGenerator.Execute.cs @@ -96,7 +96,7 @@ public static bool TryGetAccessibilityModifiers( } // Track the accessors accessibility, if explicitly set - foreach (AccessorDeclarationSyntax accessor in node.AccessorList?.Accessors ?? []) + foreach (AccessorDeclarationSyntax accessor in node.AccessorList?.Accessors ?? default) { if (accessor.Modifiers.Count == 0) { diff --git a/src/ComputeSharp.D2D1.UI.SourceGenerators/CanvasEffectPropertyGenerator.cs b/src/ComputeSharp.D2D1.UI.SourceGenerators/CanvasEffectPropertyGenerator.cs index 3bf7d0698..e2af5be9d 100644 --- a/src/ComputeSharp.D2D1.UI.SourceGenerators/CanvasEffectPropertyGenerator.cs +++ b/src/ComputeSharp.D2D1.UI.SourceGenerators/CanvasEffectPropertyGenerator.cs @@ -1,3 +1,4 @@ +using System; #if WINDOWS_UWP using ComputeSharp.D2D1.Uwp.SourceGenerators.Constants; using ComputeSharp.D2D1.Uwp.SourceGenerators.Models; @@ -136,8 +137,8 @@ public void Initialize(IncrementalGeneratorInitializationContext context) item.Hierarchy.WriteSyntax( state: item.Properties, writer: writer, - baseTypes: [], - memberCallbacks: [Execute.WritePropertyDeclarations]); + baseTypes: ReadOnlySpan.Empty, + memberCallbacks: new IndentedTextWriter.Callback>[] { Execute.WritePropertyDeclarations }); context.AddSource($"{item.Hierarchy.FullyQualifiedMetadataName}.g.cs", writer.ToString()); }); diff --git a/src/ComputeSharp.D2D1.UI.SourceGenerators/Diagnostics/Analyzers/InvalidGeneratedCanvasEffectPropertyAccessorsAnalyzer.cs b/src/ComputeSharp.D2D1.UI.SourceGenerators/Diagnostics/Analyzers/InvalidGeneratedCanvasEffectPropertyAccessorsAnalyzer.cs index 41489bab5..549e26a7d 100644 --- a/src/ComputeSharp.D2D1.UI.SourceGenerators/Diagnostics/Analyzers/InvalidGeneratedCanvasEffectPropertyAccessorsAnalyzer.cs +++ b/src/ComputeSharp.D2D1.UI.SourceGenerators/Diagnostics/Analyzers/InvalidGeneratedCanvasEffectPropertyAccessorsAnalyzer.cs @@ -26,7 +26,7 @@ namespace ComputeSharp.D2D1.WinUI.SourceGenerators; public sealed class InvalidGeneratedCanvasEffectPropertyAccessorsAnalyzer : DiagnosticAnalyzer { /// - public override ImmutableArray SupportedDiagnostics { get; } = [InvalidGeneratedCanvasEffectPropertyAccessors]; + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(InvalidGeneratedCanvasEffectPropertyAccessors); /// public override void Initialize(AnalysisContext context) diff --git a/src/ComputeSharp.D2D1.UI.SourceGenerators/Diagnostics/Analyzers/InvalidGeneratedCanvasEffectPropertyContainingTypeAnalyzer.cs b/src/ComputeSharp.D2D1.UI.SourceGenerators/Diagnostics/Analyzers/InvalidGeneratedCanvasEffectPropertyContainingTypeAnalyzer.cs index bedd55d54..94915077c 100644 --- a/src/ComputeSharp.D2D1.UI.SourceGenerators/Diagnostics/Analyzers/InvalidGeneratedCanvasEffectPropertyContainingTypeAnalyzer.cs +++ b/src/ComputeSharp.D2D1.UI.SourceGenerators/Diagnostics/Analyzers/InvalidGeneratedCanvasEffectPropertyContainingTypeAnalyzer.cs @@ -26,7 +26,7 @@ namespace ComputeSharp.D2D1.WinUI.SourceGenerators; public sealed class InvalidGeneratedCanvasEffectPropertyContainingTypeAnalyzer : DiagnosticAnalyzer { /// - public override ImmutableArray SupportedDiagnostics { get; } = [InvalidGeneratedCanvasEffectPropertyContainingType]; + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(InvalidGeneratedCanvasEffectPropertyContainingType); /// public override void Initialize(AnalysisContext context) diff --git a/src/ComputeSharp.D2D1.UI.SourceGenerators/Diagnostics/Analyzers/InvalidGeneratedCanvasEffectPropertyDeclarationAnalyzer.cs b/src/ComputeSharp.D2D1.UI.SourceGenerators/Diagnostics/Analyzers/InvalidGeneratedCanvasEffectPropertyDeclarationAnalyzer.cs index 3875dcae2..618d76fb6 100644 --- a/src/ComputeSharp.D2D1.UI.SourceGenerators/Diagnostics/Analyzers/InvalidGeneratedCanvasEffectPropertyDeclarationAnalyzer.cs +++ b/src/ComputeSharp.D2D1.UI.SourceGenerators/Diagnostics/Analyzers/InvalidGeneratedCanvasEffectPropertyDeclarationAnalyzer.cs @@ -26,13 +26,11 @@ namespace ComputeSharp.D2D1.WinUI.SourceGenerators; public sealed class InvalidGeneratedCanvasEffectPropertyDeclarationAnalyzer : DiagnosticAnalyzer { /// - public override ImmutableArray SupportedDiagnostics { get; } = - [ + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create( InvalidGeneratedCanvasEffectPropertyDeclarationIsStatic, InvalidGeneratedCanvasEffectPropertyDeclarationIsNotIncompletePartialDefinition, InvalidGeneratedCanvasEffectPropertyDeclarationReturnsByRef, - InvalidGeneratedCanvasEffectPropertyDeclarationReturnsRefLikeType - ]; + InvalidGeneratedCanvasEffectPropertyDeclarationReturnsRefLikeType); /// public override void Initialize(AnalysisContext context) diff --git a/src/ComputeSharp.D2D1.UI.SourceGenerators/Diagnostics/Analyzers/RequireCSharpLanguageVersionPreviewAnalyzer.cs b/src/ComputeSharp.D2D1.UI.SourceGenerators/Diagnostics/Analyzers/RequireCSharpLanguageVersionPreviewAnalyzer.cs index dead17818..f40f1eb3d 100644 --- a/src/ComputeSharp.D2D1.UI.SourceGenerators/Diagnostics/Analyzers/RequireCSharpLanguageVersionPreviewAnalyzer.cs +++ b/src/ComputeSharp.D2D1.UI.SourceGenerators/Diagnostics/Analyzers/RequireCSharpLanguageVersionPreviewAnalyzer.cs @@ -26,7 +26,7 @@ namespace ComputeSharp.D2D1.WinUI.SourceGenerators; public sealed class RequireCSharpLanguageVersionPreviewAnalyzer : DiagnosticAnalyzer { /// - public override ImmutableArray SupportedDiagnostics { get; } = [CSharpLanguageVersionIsNotPreview]; + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(CSharpLanguageVersionIsNotPreview); /// public override void Initialize(AnalysisContext context) diff --git a/src/ComputeSharp.D2D1.UI/CanvasEffectInvalidationType.cs b/src/ComputeSharp.D2D1.UI/CanvasEffectInvalidationType.cs index ff1330a9a..66d3d9f56 100644 --- a/src/ComputeSharp.D2D1.UI/CanvasEffectInvalidationType.cs +++ b/src/ComputeSharp.D2D1.UI/CanvasEffectInvalidationType.cs @@ -1,4 +1,6 @@ +#if !D2D1_UI_SOURCE_GENERATOR using Microsoft.Graphics.Canvas; +#endif #if WINDOWS_UWP namespace ComputeSharp.D2D1.Uwp; diff --git a/src/ComputeSharp.SourceGeneration.Hlsl/Extensions/SyntaxNodeExtensions.cs b/src/ComputeSharp.SourceGeneration.Hlsl/Extensions/SyntaxNodeExtensions.cs index 7baa38562..43baa857d 100644 --- a/src/ComputeSharp.SourceGeneration.Hlsl/Extensions/SyntaxNodeExtensions.cs +++ b/src/ComputeSharp.SourceGeneration.Hlsl/Extensions/SyntaxNodeExtensions.cs @@ -8,37 +8,9 @@ namespace ComputeSharp.SourceGeneration.Extensions; -/// -/// A with some extension methods for C# syntax nodes. -/// -internal static class SyntaxNodeExtensions +/// +internal static partial class SyntaxNodeExtensions { - /// - /// Checks whether a given is a given type declaration with or potentially with any base types, using only syntax. - /// - /// The type of declaration to check for. - /// The input to check. - /// Whether is a given type declaration with or potentially with any base types. - public static bool IsTypeDeclarationWithOrPotentiallyWithBaseTypes(this SyntaxNode node) - where T : TypeDeclarationSyntax - { - // Immediately bail if the node is not a type declaration of the specified type - if (node is not T typeDeclaration) - { - return false; - } - - // If the base types list is not empty, the type can definitely has implemented interfaces - if (typeDeclaration.BaseList is { Types.Count: > 0 }) - { - return true; - } - - // If the base types list is empty, check if the type is partial. If it is, it means - // that there could be another partial declaration with a non-empty base types list. - return typeDeclaration.Modifiers.Any(SyntaxKind.PartialKeyword); - } - /// /// Checks a value and replaces the value type to be HLSL compatible, if needed. /// diff --git a/src/ComputeSharp.SourceGeneration/ComputeSharp.SourceGeneration.projitems b/src/ComputeSharp.SourceGeneration/ComputeSharp.SourceGeneration.projitems index 413e50cc8..ca9363664 100644 --- a/src/ComputeSharp.SourceGeneration/ComputeSharp.SourceGeneration.projitems +++ b/src/ComputeSharp.SourceGeneration/ComputeSharp.SourceGeneration.projitems @@ -9,16 +9,20 @@ ComputeSharp.SourceGeneration + + + + diff --git a/src/ComputeSharp.SourceGeneration/Constants/WellKnownTrackingNames.cs b/src/ComputeSharp.SourceGeneration/Constants/WellKnownTrackingNames.cs new file mode 100644 index 000000000..edb012abc --- /dev/null +++ b/src/ComputeSharp.SourceGeneration/Constants/WellKnownTrackingNames.cs @@ -0,0 +1,22 @@ +namespace ComputeSharp.SourceGeneration.Constants; + +/// +/// The well known names for tracking steps, to test the incremental generators. +/// +internal static class WellKnownTrackingNames +{ + /// + /// The initial transform node. + /// + public const string Execute = nameof(Execute); + + /// + /// The filtered transform with just output diagnostics. + /// + public const string Diagnostics = nameof(Diagnostics); + + /// + /// The filtered transform with just output sources. + /// + public const string Output = nameof(Output); +} \ No newline at end of file diff --git a/src/ComputeSharp.SourceGeneration/Extensions/AccessibilityExtensions.cs b/src/ComputeSharp.SourceGeneration/Extensions/AccessibilityExtensions.cs new file mode 100644 index 000000000..a5e2e3498 --- /dev/null +++ b/src/ComputeSharp.SourceGeneration/Extensions/AccessibilityExtensions.cs @@ -0,0 +1,28 @@ +using Microsoft.CodeAnalysis; + +namespace ComputeSharp.SourceGeneration.Extensions; + +/// +/// Extension methods for the type. +/// +internal static class AccessibilityExtensions +{ + /// + /// Gets the expression for a given value. + /// + /// The input value. + /// The expression for . + public static string GetExpression(this Accessibility accessibility) + { + return accessibility switch + { + Accessibility.Private => "private", + Accessibility.ProtectedAndInternal => "private protected", + Accessibility.Protected => "protected", + Accessibility.Internal => "internal", + Accessibility.ProtectedOrInternal => "protected internal", + Accessibility.Public => "public", + _ => "" + }; + } +} \ No newline at end of file diff --git a/src/ComputeSharp.SourceGeneration/Extensions/CompilationExtensions.cs b/src/ComputeSharp.SourceGeneration/Extensions/CompilationExtensions.cs index 0c10fb39c..d3db8a2c1 100644 --- a/src/ComputeSharp.SourceGeneration/Extensions/CompilationExtensions.cs +++ b/src/ComputeSharp.SourceGeneration/Extensions/CompilationExtensions.cs @@ -46,6 +46,16 @@ public static bool TryBuildNamedTypeSymbolSet( return true; } + /// + /// Checks whether a given compilation (assumed to be for C#) is using the preview language version. + /// + /// The to consider for analysis. + /// Whether is using the preview language version. + public static bool IsLanguageVersionPreview(this Compilation compilation) + { + return ((CSharpCompilation)compilation).LanguageVersion == LanguageVersion.Preview; + } + /// /// Checks whether the AllowUnsafeBlocks option is set for a given compilation. /// diff --git a/src/ComputeSharp.SourceGeneration/Extensions/ISymbolExtensions.cs b/src/ComputeSharp.SourceGeneration/Extensions/ISymbolExtensions.cs index a0dd6fbe3..4ae71f797 100644 --- a/src/ComputeSharp.SourceGeneration/Extensions/ISymbolExtensions.cs +++ b/src/ComputeSharp.SourceGeneration/Extensions/ISymbolExtensions.cs @@ -27,6 +27,16 @@ public static string GetFullyQualifiedName(this ISymbol symbol) return symbol.ToDisplayString(FullyQualifiedWithoutGlobalFormat); } + /// + /// Gets the fully qualified name for a given symbol, including nullability annotations + /// + /// The input instance. + /// The fully qualified name for . + public static string GetFullyQualifiedNameWithNullabilityAnnotations(this ISymbol symbol) + { + return symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat.AddMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier)); + } + /// /// Checks whether or not a given symbol has an attribute with the specified type. /// diff --git a/src/ComputeSharp.SourceGeneration/Extensions/ITypeSymbolExtensions.cs b/src/ComputeSharp.SourceGeneration/Extensions/ITypeSymbolExtensions.cs index 2169baa36..51e2ee5d4 100644 --- a/src/ComputeSharp.SourceGeneration/Extensions/ITypeSymbolExtensions.cs +++ b/src/ComputeSharp.SourceGeneration/Extensions/ITypeSymbolExtensions.cs @@ -24,6 +24,52 @@ public static bool HasFullyQualifiedMetadataName(this ITypeSymbol symbol, string return builder.WrittenSpan.SequenceEqual(name.AsSpan()); } + /// + /// Checks whether or not a given inherits from a specified type. + /// + /// The target instance to check. + /// The full name of the type to check for inheritance. + /// Whether or not inherits from . + public static bool InheritsFromFullyQualifiedMetadataName(this ITypeSymbol typeSymbol, string name) + { + INamedTypeSymbol? baseType = typeSymbol.BaseType; + + while (baseType is not null) + { + if (baseType.HasFullyQualifiedMetadataName(name)) + { + return true; + } + + baseType = baseType.BaseType; + } + + return false; + } + + /// + /// Checks whether or not a given inherits from a specified type. + /// + /// The target instance to check. + /// The instane to check for inheritance from. + /// Whether or not inherits from . + public static bool InheritsFromType(this ITypeSymbol typeSymbol, ITypeSymbol baseTypeSymbol) + { + INamedTypeSymbol? currentBaseTypeSymbol = typeSymbol.BaseType; + + while (currentBaseTypeSymbol is not null) + { + if (SymbolEqualityComparer.Default.Equals(currentBaseTypeSymbol, baseTypeSymbol)) + { + return true; + } + + currentBaseTypeSymbol = currentBaseTypeSymbol.BaseType; + } + + return false; + } + /// /// Checks whether or not a given implements an interface of a specified type. /// diff --git a/src/ComputeSharp.SourceGeneration/Extensions/IncrementalValuesProviderExtensions.cs b/src/ComputeSharp.SourceGeneration/Extensions/IncrementalValuesProviderExtensions.cs new file mode 100644 index 000000000..5f865c6b2 --- /dev/null +++ b/src/ComputeSharp.SourceGeneration/Extensions/IncrementalValuesProviderExtensions.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using ComputeSharp.SourceGeneration.Helpers; +using Microsoft.CodeAnalysis; + +namespace ComputeSharp.SourceGeneration.Extensions; + +/// +/// Extension methods for . +/// +internal static class IncrementalValuesProviderExtensions +{ + /// + /// Groups items in a given sequence by a specified key. + /// + /// The type of value that this source provides access to. + /// The type of resulting key elements. + /// The type of resulting projected elements. + /// The input instance. + /// The key selection . + /// The element selection . + /// An with the grouped results. + public static IncrementalValuesProvider<(TKey Key, EquatableArray Right)> GroupBy( + this IncrementalValuesProvider source, + Func keySelector, + Func elementSelector) + where TValues : IEquatable + where TKey : IEquatable + where TElement : IEquatable + { + return source.Collect().SelectMany((item, token) => + { + Dictionary.Builder> map = new(); + + foreach (TValues value in item) + { + TKey key = keySelector(value); + TElement element = elementSelector(value); + + if (!map.TryGetValue(key, out ImmutableArray.Builder builder)) + { + builder = ImmutableArray.CreateBuilder(); + + map.Add(key, builder); + } + + builder.Add(element); + } + + token.ThrowIfCancellationRequested(); + + ImmutableArray<(TKey Key, EquatableArray Elements)>.Builder result = + ImmutableArray.CreateBuilder<(TKey, EquatableArray)>(); + + foreach (KeyValuePair.Builder> entry in map) + { + result.Add((entry.Key, entry.Value.ToImmutable())); + } + + return result; + }); + } +} \ No newline at end of file diff --git a/src/ComputeSharp.SourceGeneration/Extensions/IndentedTextWriterExtensions.cs b/src/ComputeSharp.SourceGeneration/Extensions/IndentedTextWriterExtensions.cs index 262e6aa2b..667eb7d36 100644 --- a/src/ComputeSharp.SourceGeneration/Extensions/IndentedTextWriterExtensions.cs +++ b/src/ComputeSharp.SourceGeneration/Extensions/IndentedTextWriterExtensions.cs @@ -19,7 +19,12 @@ internal static class IndentedTextWriterExtensions /// The instance to write into. /// The name of the generator. /// Whether to use fully qualified type names or not. - public static void WriteGeneratedAttributes(this IndentedTextWriter writer, string generatorName, bool useFullyQualifiedTypeNames = true) + /// Whether to also include the attribute for non-user code. + public static void WriteGeneratedAttributes( + this IndentedTextWriter writer, + string generatorName, + bool useFullyQualifiedTypeNames = true, + bool includeNonUserCodeAttributes = true) { // We can use this class to get the assembly, as all files for generators are just included // via shared projects. As such, the assembly will be the same as the generator type itself. @@ -28,14 +33,22 @@ public static void WriteGeneratedAttributes(this IndentedTextWriter writer, stri if (useFullyQualifiedTypeNames) { writer.WriteLine($$"""[global::System.CodeDom.Compiler.GeneratedCode("{{generatorName}}", "{{assemblyVersion}}")]"""); - writer.WriteLine($$"""[global::System.Diagnostics.DebuggerNonUserCode]"""); - writer.WriteLine($$"""[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]"""); + + if (includeNonUserCodeAttributes) + { + writer.WriteLine($$"""[global::System.Diagnostics.DebuggerNonUserCode]"""); + writer.WriteLine($$"""[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]"""); + } } else { writer.WriteLine($$"""[GeneratedCode("{{generatorName}}", "{{assemblyVersion}}")]"""); - writer.WriteLine($$"""[DebuggerNonUserCode]"""); - writer.WriteLine($$"""[ExcludeFromCodeCoverage]"""); + + if (includeNonUserCodeAttributes) + { + writer.WriteLine($$"""[DebuggerNonUserCode]"""); + writer.WriteLine($$"""[ExcludeFromCodeCoverage]"""); + } } } diff --git a/src/ComputeSharp.SourceGeneration/Extensions/SyntaxNodeExtensions.cs b/src/ComputeSharp.SourceGeneration/Extensions/SyntaxNodeExtensions.cs new file mode 100644 index 000000000..28aad2725 --- /dev/null +++ b/src/ComputeSharp.SourceGeneration/Extensions/SyntaxNodeExtensions.cs @@ -0,0 +1,38 @@ +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace ComputeSharp.SourceGeneration.Extensions; + +/// +/// A with some extension methods for C# syntax nodes. +/// +internal static partial class SyntaxNodeExtensions +{ + /// + /// Checks whether a given is a given type declaration with or potentially with any base types, using only syntax. + /// + /// The type of declaration to check for. + /// The input to check. + /// Whether is a given type declaration with or potentially with any base types. + public static bool IsTypeDeclarationWithOrPotentiallyWithBaseTypes(this SyntaxNode node) + where T : TypeDeclarationSyntax + { + // Immediately bail if the node is not a type declaration of the specified type + if (node is not T typeDeclaration) + { + return false; + } + + // If the base types list is not empty, the type can definitely has implemented interfaces + if (typeDeclaration.BaseList is { Types.Count: > 0 }) + { + return true; + } + + // If the base types list is empty, check if the type is partial. If it is, it means + // that there could be another partial declaration with a non-empty base types list. + return typeDeclaration.Modifiers.Any(SyntaxKind.PartialKeyword); + } +} \ No newline at end of file diff --git a/src/ComputeSharp.SourceGeneration/Helpers/IndentedTextWriter.cs b/src/ComputeSharp.SourceGeneration/Helpers/IndentedTextWriter.cs index 37c51870c..b7b50ca7e 100644 --- a/src/ComputeSharp.SourceGeneration/Helpers/IndentedTextWriter.cs +++ b/src/ComputeSharp.SourceGeneration/Helpers/IndentedTextWriter.cs @@ -4,6 +4,7 @@ using System; using System.ComponentModel; +using System.Globalization; using System.Runtime.CompilerServices; using System.Text; @@ -176,11 +177,31 @@ public void Write([InterpolatedStringHandlerArgument("")] ref WriteInterpolatedS _ = this; } + /// + /// Writes content to the underlying buffer depending on an input condition. + /// + /// The condition to use to decide whether or not to write content. + /// The content to write. + /// Whether the input content is multiline. + public void WriteIf(bool condition, string content, bool isMultiline = false) + { + if (condition) + { + Write(content.AsSpan(), isMultiline); + } + } + /// /// Writes a line to the underlying buffer. /// - public void WriteLine() + /// Indicates whether to skip adding the line if there already is one. + public void WriteLine(bool skipIfPresent = false) { + if (skipIfPresent && this.builder.WrittenSpan is [.., '\n', '\n']) + { + return; + } + this.builder.Add(DefaultNewLine); } @@ -342,7 +363,7 @@ public void AppendFormatted(T value, string? format) { if (value is IFormattable) { - this.writer.Write(((IFormattable)value).ToString(format)); + this.writer.Write(((IFormattable)value).ToString(format, CultureInfo.InvariantCulture)); } else if (value is not null) {