From e5b75d2dc853b917998f85304aa12f24419ab5fb Mon Sep 17 00:00:00 2001 From: Manuel de la Pena Date: Wed, 16 Oct 2024 13:17:08 -0400 Subject: [PATCH 01/22] [Rgen] Provide all the wiring to allow to emit code. Add the following: 1. Code needed to register the changes in the compiler pipeline. 2. Code that will add the using statements for the generated code that match those in the binding file. 3. Code that allows to write tabbed code in a way we do not have to keep track of indentation. We have the first unit test that verifies code generation by checking the using statements. --- src/bgen/Attributes.cs | 9 - src/bgen/Extensions/PlatformNameExtensions.cs | 116 +++------- .../Extensions/PlatformNameExtensionsBgen.cs | 102 +++++++++ src/bgen/PlatformName.cs | 8 + .../AttributesNames.cs | 9 + .../BindingSourceGeneratorGenerator.cs | 142 +++++++++++- .../Context/ClassBindingContext.cs | 16 ++ .../Context/ContextFactory.cs | 21 ++ .../Context/ISymbolBindingContext.cs | 20 ++ .../Context/RootBindingContext.cs | 99 +++++++++ .../Context/SymbolBindingContext.cs | 36 +++ .../Emitters/ClassEmitter.cs | 27 +++ .../Emitters/EmitterFactory.cs | 23 ++ .../Emitters/EnumEmitter.cs | 21 ++ .../Emitters/ICodeEmitter.cs | 14 ++ .../Emitters/InterfaceEmitter.cs | 18 ++ .../Microsoft.Macios.Generator.csproj | 15 ++ .../TabbedStringBuilder.cs | 188 ++++++++++++++++ .../generator/PlatformNameExtensionsTests.cs | 5 +- .../BindingSourceGeneratorGeneratorTests.cs | 50 +++-- .../TabbedStringBuilderTests.cs | 205 ++++++++++++++++++ 21 files changed, 1027 insertions(+), 117 deletions(-) create mode 100644 src/bgen/Extensions/PlatformNameExtensionsBgen.cs create mode 100644 src/bgen/PlatformName.cs create mode 100644 src/rgen/Microsoft.Macios.Generator/AttributesNames.cs create mode 100644 src/rgen/Microsoft.Macios.Generator/Context/ClassBindingContext.cs create mode 100644 src/rgen/Microsoft.Macios.Generator/Context/ContextFactory.cs create mode 100644 src/rgen/Microsoft.Macios.Generator/Context/ISymbolBindingContext.cs create mode 100644 src/rgen/Microsoft.Macios.Generator/Context/RootBindingContext.cs create mode 100644 src/rgen/Microsoft.Macios.Generator/Context/SymbolBindingContext.cs create mode 100644 src/rgen/Microsoft.Macios.Generator/Emitters/ClassEmitter.cs create mode 100644 src/rgen/Microsoft.Macios.Generator/Emitters/EmitterFactory.cs create mode 100644 src/rgen/Microsoft.Macios.Generator/Emitters/EnumEmitter.cs create mode 100644 src/rgen/Microsoft.Macios.Generator/Emitters/ICodeEmitter.cs create mode 100644 src/rgen/Microsoft.Macios.Generator/Emitters/InterfaceEmitter.cs create mode 100644 src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs create mode 100644 tests/rgen/Microsoft.Macios.Generator.Tests/TabbedStringBuilderTests.cs diff --git a/src/bgen/Attributes.cs b/src/bgen/Attributes.cs index 0096d35b1d5f..25ad9f46cd6b 100644 --- a/src/bgen/Attributes.cs +++ b/src/bgen/Attributes.cs @@ -883,15 +883,6 @@ public class NoMethodAttribute : Attribute { } #if NET -public enum PlatformName : byte { - None, - MacOSX, - iOS, - WatchOS, - TvOS, - MacCatalyst, -} - public enum AvailabilityKind { Introduced, Deprecated, diff --git a/src/bgen/Extensions/PlatformNameExtensions.cs b/src/bgen/Extensions/PlatformNameExtensions.cs index 982e5eb1bd8f..f78d318c9954 100644 --- a/src/bgen/Extensions/PlatformNameExtensions.cs +++ b/src/bgen/Extensions/PlatformNameExtensions.cs @@ -1,132 +1,74 @@ -using System; -using System.IO; -using ObjCRuntime; -using Xamarin.Utils; +using System.Diagnostics.CodeAnalysis; public static class PlatformNameExtensions { - public static string GetApplicationClassName (this PlatformName currentPlatform) + public static bool TryGetApplicationClassName (this PlatformName currentPlatform, [NotNullWhen(true)]out string? className) { switch (currentPlatform) { case PlatformName.iOS: case PlatformName.WatchOS: case PlatformName.TvOS: case PlatformName.MacCatalyst: - return "UIApplication"; + className = "UIApplication"; + return true; case PlatformName.MacOSX: - return "NSApplication"; + className = "NSApplication"; + return true; default: - throw new BindingException (1047, currentPlatform); + className = null; + return false; } } - public static int GetXamcoreVersion (this PlatformName currentPlatform) - { -#if NET - return 4; -#else - switch (currentPlatform) { - case PlatformName.MacOSX: - case PlatformName.iOS: - return 2; - case PlatformName.TvOS: - case PlatformName.WatchOS: - return 3; - default: - return 4; - } -#endif - } - - public static string GetCoreImageMap (this PlatformName currentPlatform) + public static bool TryGetCoreImageMap (this PlatformName currentPlatform, [NotNullWhen(true)]out string? coreImageMap) { switch (currentPlatform) { case PlatformName.iOS: case PlatformName.WatchOS: case PlatformName.TvOS: case PlatformName.MacCatalyst: - return "CoreImage"; + coreImageMap = "CoreImage"; + return true; case PlatformName.MacOSX: - return "Quartz"; + coreImageMap = "Quartz"; + return true; default: - throw new BindingException (1047, currentPlatform); + coreImageMap = null; + return false; } } - public static string GetCoreServicesMap (this PlatformName currentPlatform) + public static bool TryGetCoreServicesMap (this PlatformName currentPlatform, [NotNullWhen(true)]out string? coreServicesMap) { switch (currentPlatform) { case PlatformName.iOS: case PlatformName.WatchOS: case PlatformName.TvOS: case PlatformName.MacCatalyst: - return "MobileCoreServices"; - case PlatformName.MacOSX: - return "CoreServices"; - default: - throw new BindingException (1047, currentPlatform); - } - } - - public static string GetPDFKitMap (this PlatformName currentPlatform) - { - switch (currentPlatform) { - case PlatformName.iOS: - case PlatformName.MacCatalyst: - return "PDFKit"; - case PlatformName.MacOSX: - return "Quartz"; - default: - throw new BindingException (1047, currentPlatform); - } - } - - public static ApplePlatform AsApplePlatform (this PlatformName platform) - { - switch (platform) { - case PlatformName.iOS: - return ApplePlatform.iOS; - case PlatformName.TvOS: - return ApplePlatform.TVOS; - case PlatformName.MacCatalyst: - return ApplePlatform.MacCatalyst; + coreServicesMap = "MobileCoreServices"; + return true; case PlatformName.MacOSX: - return ApplePlatform.MacOSX; - case PlatformName.WatchOS: - return ApplePlatform.WatchOS; - case PlatformName.None: - return ApplePlatform.None; + coreServicesMap = "CoreServices"; + return true; default: - throw new ArgumentOutOfRangeException (nameof (platform), platform, $"Unknown platform: {platform}"); + coreServicesMap = null; + return false; } } - static string GetSdkRoot (this PlatformName currentPlatform) + public static bool TryGetPDFKitMap (this PlatformName currentPlatform, [NotNullWhen(true)]out string? pdfKitMap) { switch (currentPlatform) { case PlatformName.iOS: - case PlatformName.WatchOS: - case PlatformName.TvOS: case PlatformName.MacCatalyst: - var sdkRoot = Environment.GetEnvironmentVariable ("MD_MTOUCH_SDK_ROOT"); - if (string.IsNullOrEmpty (sdkRoot)) - sdkRoot = "/Library/Frameworks/Xamarin.iOS.framework/Versions/Current"; - return sdkRoot; + pdfKitMap = "PDFKit"; + return true; case PlatformName.MacOSX: - var macSdkRoot = Environment.GetEnvironmentVariable ("XamarinMacFrameworkRoot"); - if (string.IsNullOrEmpty (macSdkRoot)) - macSdkRoot = "/Library/Frameworks/Xamarin.Mac.framework/Versions/Current"; - return macSdkRoot; + pdfKitMap = "Quartz"; + return true; default: - throw new BindingException (1047, currentPlatform); + pdfKitMap = null; + return false; } } - - public static string GetPath (this PlatformName currentPlatform, params string [] paths) - { - var fullPaths = new string [paths.Length + 1]; - fullPaths [0] = currentPlatform.GetSdkRoot (); - Array.Copy (paths, 0, fullPaths, 1, paths.Length); - return Path.Combine (fullPaths); - } } diff --git a/src/bgen/Extensions/PlatformNameExtensionsBgen.cs b/src/bgen/Extensions/PlatformNameExtensionsBgen.cs new file mode 100644 index 000000000000..5c9d90d9c20a --- /dev/null +++ b/src/bgen/Extensions/PlatformNameExtensionsBgen.cs @@ -0,0 +1,102 @@ +using System; +using System.IO; +using Xamarin.Utils; + +public static class PlatformNameExtensionsBgen { + + // wrapper that allows us to use the same code for rgen and bgen + public static string GetApplicationClassName (this PlatformName currentPlatform) + { + if (currentPlatform.TryGetApplicationClassName (out var applicationClassName)) + return applicationClassName; + throw new BindingException (1047, currentPlatform); + } + + public static string GetCoreImageMap (this PlatformName currentPlatform) + { + if (currentPlatform.TryGetCoreImageMap (out var coreImageMap)) + return coreImageMap; + throw new BindingException (1047, currentPlatform); + } + + public static string GetCoreServicesMap (this PlatformName currentPlatform) + { + if (currentPlatform.TryGetCoreServicesMap(out var coreServicesMap)) + return coreServicesMap; + throw new BindingException (1047, currentPlatform); + } + + public static string GetPDFKitMap (this PlatformName currentPlatform) + { + if (currentPlatform.TryGetPDFKitMap (out var pdfKitMap)) + return pdfKitMap; + throw new BindingException (1047, currentPlatform); + } + + public static int GetXamcoreVersion (this PlatformName currentPlatform) + { +#if NET + return 4; +#else + switch (currentPlatform) { + case PlatformName.MacOSX: + case PlatformName.iOS: + return 2; + case PlatformName.TvOS: + case PlatformName.WatchOS: + return 3; + default: + return 4; + } +#endif + } + + public static ApplePlatform AsApplePlatform (this PlatformName platform) + { + switch (platform) { + case PlatformName.iOS: + return ApplePlatform.iOS; + case PlatformName.TvOS: + return ApplePlatform.TVOS; + case PlatformName.MacCatalyst: + return ApplePlatform.MacCatalyst; + case PlatformName.MacOSX: + return ApplePlatform.MacOSX; + case PlatformName.WatchOS: + return ApplePlatform.WatchOS; + case PlatformName.None: + return ApplePlatform.None; + default: + throw new ArgumentOutOfRangeException (nameof (platform), platform, $"Unknown platform: {platform}"); + } + } + + static string GetSdkRoot (this PlatformName currentPlatform) + { + switch (currentPlatform) { + case PlatformName.iOS: + case PlatformName.WatchOS: + case PlatformName.TvOS: + case PlatformName.MacCatalyst: + var sdkRoot = Environment.GetEnvironmentVariable ("MD_MTOUCH_SDK_ROOT"); + if (string.IsNullOrEmpty (sdkRoot)) + sdkRoot = "/Library/Frameworks/Xamarin.iOS.framework/Versions/Current"; + return sdkRoot; + case PlatformName.MacOSX: + var macSdkRoot = Environment.GetEnvironmentVariable ("XamarinMacFrameworkRoot"); + if (string.IsNullOrEmpty (macSdkRoot)) + macSdkRoot = "/Library/Frameworks/Xamarin.Mac.framework/Versions/Current"; + return macSdkRoot; + default: + throw new BindingException (1047, currentPlatform); + } + } + + public static string GetPath (this PlatformName currentPlatform, params string [] paths) + { + var fullPaths = new string [paths.Length + 1]; + fullPaths [0] = currentPlatform.GetSdkRoot (); + Array.Copy (paths, 0, fullPaths, 1, paths.Length); + return Path.Combine (fullPaths); + } +} diff --git a/src/bgen/PlatformName.cs b/src/bgen/PlatformName.cs new file mode 100644 index 000000000000..c17caa5d6bf4 --- /dev/null +++ b/src/bgen/PlatformName.cs @@ -0,0 +1,8 @@ +public enum PlatformName : byte { + None, + MacOSX, + iOS, + WatchOS, + TvOS, + MacCatalyst, +} diff --git a/src/rgen/Microsoft.Macios.Generator/AttributesNames.cs b/src/rgen/Microsoft.Macios.Generator/AttributesNames.cs new file mode 100644 index 000000000000..62ac3b632b01 --- /dev/null +++ b/src/rgen/Microsoft.Macios.Generator/AttributesNames.cs @@ -0,0 +1,9 @@ +namespace Microsoft.Macios.Generator; + +/// +/// Contains all the names of the attributes that are used by the binding generator. +/// +public static class AttributesNames { + + public static readonly string BindingAttribute = "ObjCBindings.BindingTypeAttribute"; +} diff --git a/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs b/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs index fcc630f160ce..4a7e286aaebf 100644 --- a/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs +++ b/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs @@ -1,16 +1,26 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Runtime.CompilerServices; using System.Text; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; +using Microsoft.Macios.Generator.Context; +using Microsoft.Macios.Generator.Emitters; namespace Microsoft.Macios.Generator; /// -/// A sample source generator that creates a custom report based on class properties. The target class should be annotated with the 'Generators.ReportAttribute' attribute. -/// When using the source code as a baseline, an incremental source generator is preferable because it reduces the performance overhead. +/// A sample source generator that creates a custom report based on class properties. The target class should be +/// annotated with the 'Generators.ReportAttribute' attribute. +/// When using the source code as a baseline, an incremental source generator is preferable because it reduces +/// the performance overhead. /// [Generator] public class BindingSourceGeneratorGenerator : IIncrementalGenerator { + /// public void Initialize (IncrementalGeneratorInitializationContext context) { // Add the binding generator attributes to the compilation. This are only available when the @@ -19,6 +29,134 @@ public void Initialize (IncrementalGeneratorInitializationContext context) context.RegisterPostInitializationOutput (ctx => ctx.AddSource ( fileName, SourceText.From (content, Encoding.UTF8))); } + + // binding can use different 'types'. To be able to generate the code we are going to add a different + // function for each of the 'types' we are interested in generating that will be added to the compiler + // pipeline + AddPipeline (context); + AddPipeline (context); + AddPipeline (context); + } + + /// + /// Generic method that adds a provider and a code generator to the pipeline. + /// + /// The compilation context + /// The base type declaration that we are going to generate. + static void AddPipeline (IncrementalGeneratorInitializationContext context) where T: BaseTypeDeclarationSyntax + { + var provider = context.SyntaxProvider + .CreateSyntaxProvider ( + static (s, _) => s is T, + (ctx, _) => GetDeclarationForSourceGen (ctx)) + .Where (t => t.BindingAttributeFound) + .Select ((t, _) => t.Declaration); + + context.RegisterSourceOutput (context.CompilationProvider.Combine (provider.Collect ()), + ((ctx, t) => GenerateCode (ctx, t.Left, t.Right))); + } + + /// + /// Generic method that can be used to filter/match a BaseTypeDeclarationSyntax with the BindingTypeAttribute. + /// Because our generator is focused only on Enum, Interface and Class declarations we can use a generic method + /// that will match the type + the presence of the attribute. + /// + /// Context used by the generator. + /// The BaseTypeDeclarationSyntax we are interested in. + /// + static (T Declaration, bool BindingAttributeFound) GetDeclarationForSourceGen (GeneratorSyntaxContext context) + where T : BaseTypeDeclarationSyntax + { + var classDeclarationSyntax = Unsafe.As (context.Node); + + // Go through all attributes of the class. + foreach (AttributeListSyntax attributeListSyntax in classDeclarationSyntax.AttributeLists) + foreach (AttributeSyntax attributeSyntax in attributeListSyntax.Attributes) { + if (context.SemanticModel.GetSymbolInfo (attributeSyntax).Symbol is not IMethodSymbol attributeSymbol) + continue; // if we can't get the symbol, ignore it + + string attributeName = attributeSymbol.ContainingType.ToDisplayString (); + + // Check the full name of the [Binding] attribute. + if (attributeName == AttributesNames.BindingAttribute) + return (classDeclarationSyntax, true); + } + + return (classDeclarationSyntax, false); + } + + /// + /// Collect the using statements from the class declaration root syntaxt tree and add them to the string builder + /// that will be used to generate the code. This way we ensure that we have all the namespaces needed by the + /// generated code. + /// + /// Root syntax tree of the base type declaration. + /// String builder that will be used for the generated code. + static void CollectUsingStatements (SyntaxTree tree, TabbedStringBuilder sb) + { + // collect all using from the syntax tree, add them to a hash to make sure that we don't have duplicates + // and add those usings that we do know we need for bindings. + var usingDirectives = tree.GetRoot () + .DescendantNodes () + .OfType () + .Select (d => d.Name.ToString ()).ToArray (); + var usingDirectivesToKeep = new HashSet (usingDirectives) { + // add the using statements that we know we need and print them to the sb + }; + foreach (var ns in usingDirectivesToKeep) { + if (string.IsNullOrEmpty (ns)) + continue; + sb.AppendFormatLine ("using {0};", ns); + } + } + + /// + /// Generic method that allows to call a emitter for ta type that will emit the binding code. All code generation + /// is very similar. Get create a tabbed string builder to write the code with the needed using statemens from + /// the original syntax tree and we pass it to the emitter that will generate the code. + /// + /// The generator context. + /// The compilation unit. + /// The base type declarations marked by the BindingTypeAttribute. + /// The type of type declaration. + static void GenerateCode (SourceProductionContext context, Compilation compilation, + ImmutableArray baseTypeDeclarations) where T : BaseTypeDeclarationSyntax + { + var rootContext = new RootBindingContext (compilation); + foreach (var baseTypeDeclarationSyntax in baseTypeDeclarations) { + var semanticModel = compilation.GetSemanticModel (baseTypeDeclarationSyntax.SyntaxTree); + if (semanticModel.GetDeclaredSymbol (baseTypeDeclarationSyntax) is not INamedTypeSymbol namedTypeSymbol) + continue; + + // init sb and add all the using statements from the base type declaration + var sb = new TabbedStringBuilder (new()); + sb.AppendLine("// "); + CollectUsingStatements (baseTypeDeclarationSyntax.SyntaxTree, sb); + + // enable nullable! + sb.AppendLine (); + sb.AppendFormatLine ("#nullable enable"); + sb.AppendLine (); + + // delegate semantic model and syntax tree analysis to the emitter who will generate the code and knows + // best + if (ContextFactory.TryCreate (rootContext, semanticModel, namedTypeSymbol, baseTypeDeclarationSyntax, out var symbolBindingContext) + && EmitterFactory.TryCreate (symbolBindingContext, sb, out var emitter)) { + if (!emitter.TryValidate (out var diagnostics)) { + continue; + } + emitter.Emit (); + + // only add file when we do generate code + var code = sb.ToString (); + context.AddSource ($"{emitter.SymbolName}.g.cs", SourceText.From (code, Encoding.UTF8)); + } else { + // we don't have a emitter for this type, so we can't generate the code, add a diagnostic letting the + // user we do not support what he is trying to do + continue; + } + + } } } diff --git a/src/rgen/Microsoft.Macios.Generator/Context/ClassBindingContext.cs b/src/rgen/Microsoft.Macios.Generator/Context/ClassBindingContext.cs new file mode 100644 index 000000000000..8278c6437e6f --- /dev/null +++ b/src/rgen/Microsoft.Macios.Generator/Context/ClassBindingContext.cs @@ -0,0 +1,16 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Microsoft.Macios.Generator.Context; + +class ClassBindingContext : SymbolBindingContext { + public string RegisterName { get; init; } + + public ClassBindingContext (RootBindingContext context, SemanticModel semanticModel, + INamedTypeSymbol symbol, ClassDeclarationSyntax declarationSyntax) + : base (context, semanticModel, symbol, declarationSyntax) + { + RegisterName = + symbol.Name; //TODO: placeholder -> should this be extracted from the BindingTypeAttribute + } +} diff --git a/src/rgen/Microsoft.Macios.Generator/Context/ContextFactory.cs b/src/rgen/Microsoft.Macios.Generator/Context/ContextFactory.cs new file mode 100644 index 000000000000..98ad17c9fc00 --- /dev/null +++ b/src/rgen/Microsoft.Macios.Generator/Context/ContextFactory.cs @@ -0,0 +1,21 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Microsoft.Macios.Generator.Context; + +static class ContextFactory { + public static bool TryCreate (RootBindingContext context, SemanticModel semanticModel, + INamedTypeSymbol symbol, T declarationSyntax, [NotNullWhen(true)] out ISymbolBindingContext? bindingContext) where T : BaseTypeDeclarationSyntax + { + bindingContext = declarationSyntax switch { + ClassDeclarationSyntax c => Unsafe.As> ( + new ClassBindingContext (context, semanticModel, symbol, c)), + EnumDeclarationSyntax => new SymbolBindingContext (context, semanticModel, symbol, declarationSyntax), + _ => null + }; + return bindingContext is not null; + } +} diff --git a/src/rgen/Microsoft.Macios.Generator/Context/ISymbolBindingContext.cs b/src/rgen/Microsoft.Macios.Generator/Context/ISymbolBindingContext.cs new file mode 100644 index 000000000000..c4e293fbac7f --- /dev/null +++ b/src/rgen/Microsoft.Macios.Generator/Context/ISymbolBindingContext.cs @@ -0,0 +1,20 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Microsoft.Macios.Generator.Context; + +/// +/// Interface that represents a symbol binding context. We use an interface to allow the usage of a coveriance type, +/// because it removes the need to a cast. +/// +/// The base type declaration whose context we have +interface ISymbolBindingContext where T : BaseTypeDeclarationSyntax +{ + T DeclarationSyntax { get; } + string Namespace { get; } + string SymbolName { get; } + RootBindingContext RootBindingContext { get; init; } + SemanticModel SemanticModel { get; init; } + INamedTypeSymbol Symbol { get; init; } + bool IsStatic { get; } +} diff --git a/src/rgen/Microsoft.Macios.Generator/Context/RootBindingContext.cs b/src/rgen/Microsoft.Macios.Generator/Context/RootBindingContext.cs new file mode 100644 index 000000000000..28a1b8caf5b2 --- /dev/null +++ b/src/rgen/Microsoft.Macios.Generator/Context/RootBindingContext.cs @@ -0,0 +1,99 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using Microsoft.CodeAnalysis; + +namespace Microsoft.Macios.Generator.Context; + +/// +/// Shared context through the entire code generation. This context allows to collect data that will be +/// later use to generate the Trampoline.g.cs file. once all classed are processed. +/// +/// The class also provides a number or properties that will allow to determine the platform we are binding and access +/// to the current compilation. +/// +class RootBindingContext { + readonly Dictionary _libraries = new(); + + public PlatformName CurrentPlatform { get; set; } + public Compilation Compilation { get; set; } + public bool BindThirdPartyLibrary { get; set; } + + public RootBindingContext (Compilation compilation) + { + Compilation = compilation; + CurrentPlatform = PlatformName.None; + // use the reference assembly to determine what platform we are binding + foreach (var referencedAssemblyName in compilation.ReferencedAssemblyNames) { + switch (referencedAssemblyName.Name) { + case "Microsoft.iOS": + CurrentPlatform = PlatformName.iOS; + break; + case "Microsoft.MacCatalyst": + CurrentPlatform = PlatformName.MacCatalyst; + break; + case "Microsoft.macOS": + CurrentPlatform = PlatformName.MacOSX; + break; + case "Microsoft.tvOS": + CurrentPlatform = PlatformName.TvOS; + break; + default: + CurrentPlatform = PlatformName.None; + break; + } + } + } + + // TODO: clean code coming from the old generator + public bool TryComputeLibraryName (string attributeLibraryName, string typeNamespace, + [NotNullWhen (true)] out string? libraryName, + out string? libraryPath) + { + libraryPath = null; + + if (!string.IsNullOrEmpty (attributeLibraryName)) { + // Remapped + libraryName = attributeLibraryName; + if (libraryName [0] == '+') { + switch (libraryName) { + case "+CoreImage": + CurrentPlatform.TryGetCoreImageMap (out libraryName); + break; + case "+CoreServices": + CurrentPlatform.TryGetCoreServicesMap (out libraryName); + break; + case "+PDFKit": + libraryName = "PdfKit"; + CurrentPlatform.TryGetPDFKitMap (out libraryPath); + break; + } + } else { + // we get something in LibraryName from FieldAttribute so we asume + // it is a path to a library, so we save the path and change library name + // to a valid identifier if needed + libraryPath = libraryName; + // without extension makes more sense, but we can't change it since it breaks compat + if (BindThirdPartyLibrary) { + libraryName = Path.GetFileName (libraryName); + } else { + libraryName = Path.GetFileNameWithoutExtension (libraryName); + } + + if (libraryName.Contains ('.')) + libraryName = libraryName.Replace (".", string.Empty); + } + } else if (BindThirdPartyLibrary) { + // User should provide a LibraryName + libraryName = null; + return false; + } else { + libraryName = typeNamespace; + } + + if (!_libraries.ContainsKey (libraryName)) + _libraries.Add (libraryName, libraryPath); + + return true; + } +} diff --git a/src/rgen/Microsoft.Macios.Generator/Context/SymbolBindingContext.cs b/src/rgen/Microsoft.Macios.Generator/Context/SymbolBindingContext.cs new file mode 100644 index 000000000000..1750ed728a81 --- /dev/null +++ b/src/rgen/Microsoft.Macios.Generator/Context/SymbolBindingContext.cs @@ -0,0 +1,36 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Microsoft.Macios.Generator.Context; + +class SymbolBindingContext { + + public RootBindingContext RootBindingContext { get; init; } + public SemanticModel SemanticModel { get; init; } + public INamedTypeSymbol Symbol { get; init; } + + public bool IsStatic => Symbol.IsStatic; + + public SymbolBindingContext(RootBindingContext rootBindingContext, + SemanticModel semanticModel, INamedTypeSymbol symbol) + { + RootBindingContext = rootBindingContext; + SemanticModel = semanticModel; + Symbol = symbol; + } + +} + +class SymbolBindingContext : SymbolBindingContext, ISymbolBindingContext where T : BaseTypeDeclarationSyntax { + + public T DeclarationSyntax { get; } + public string Namespace => Symbol.ContainingNamespace.ToDisplayString(); + public string SymbolName => Symbol.Name; + + public SymbolBindingContext(RootBindingContext rootBindingContext, + SemanticModel semanticModel, INamedTypeSymbol symbol, T declarationSyntax) + : base(rootBindingContext, semanticModel, symbol) + { + DeclarationSyntax = declarationSyntax; + } +} diff --git a/src/rgen/Microsoft.Macios.Generator/Emitters/ClassEmitter.cs b/src/rgen/Microsoft.Macios.Generator/Emitters/ClassEmitter.cs new file mode 100644 index 000000000000..023ee9b6922b --- /dev/null +++ b/src/rgen/Microsoft.Macios.Generator/Emitters/ClassEmitter.cs @@ -0,0 +1,27 @@ +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis; +using Microsoft.Macios.Generator.Context; + +namespace Microsoft.Macios.Generator.Emitters; + +class ClassEmitter (ClassBindingContext context, TabbedStringBuilder builder) : ICodeEmitter { + public string SymbolName => context.SymbolName; + + public bool TryValidate ([NotNullWhen (false)] out ImmutableArray? diagnostics) + { + diagnostics = null; + return true; + } + + public void Emit () + { + // add the namespace and the class declaration + using (var namespaceBlock = builder.CreateBlock ($"namespace {context.Namespace}", true)) { + using (var classBlock = namespaceBlock.CreateBlock ($"public partial class {SymbolName}", true)) + { + classBlock.AppendLine("// TODO: add binding code here"); + } + } + } +} diff --git a/src/rgen/Microsoft.Macios.Generator/Emitters/EmitterFactory.cs b/src/rgen/Microsoft.Macios.Generator/Emitters/EmitterFactory.cs new file mode 100644 index 000000000000..0f2f351a7bff --- /dev/null +++ b/src/rgen/Microsoft.Macios.Generator/Emitters/EmitterFactory.cs @@ -0,0 +1,23 @@ +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.Macios.Generator.Context; + +namespace Microsoft.Macios.Generator.Emitters; + +/// +/// Returns the emitter that is related to the provided declaration type. +/// +static class EmitterFactory { + public static bool TryCreate (ISymbolBindingContext context, TabbedStringBuilder builder, + [NotNullWhen(true)]out ICodeEmitter? emitter) + where T : BaseTypeDeclarationSyntax + { + emitter = context switch { + ClassBindingContext classContext => new ClassEmitter (classContext, builder), + ISymbolBindingContext enumContext => new EnumEmitter (enumContext, builder), + ISymbolBindingContext interfaceContext => new InterfaceEmitter (interfaceContext, builder), + _ => null + }; + return emitter is not null; + } +} diff --git a/src/rgen/Microsoft.Macios.Generator/Emitters/EnumEmitter.cs b/src/rgen/Microsoft.Macios.Generator/Emitters/EnumEmitter.cs new file mode 100644 index 000000000000..2f1910947554 --- /dev/null +++ b/src/rgen/Microsoft.Macios.Generator/Emitters/EnumEmitter.cs @@ -0,0 +1,21 @@ +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.Macios.Generator.Context; + +namespace Microsoft.Macios.Generator.Emitters; + +class EnumEmitter (ISymbolBindingContext context, TabbedStringBuilder builder) + : ICodeEmitter { + + public string SymbolName => $"{context.SymbolName}Extensions"; + + public bool TryValidate ([NotNullWhen(false)] out ImmutableArray? diagnostics) + { + diagnostics = null; + return true; + } + + public void Emit () { } +} diff --git a/src/rgen/Microsoft.Macios.Generator/Emitters/ICodeEmitter.cs b/src/rgen/Microsoft.Macios.Generator/Emitters/ICodeEmitter.cs new file mode 100644 index 000000000000..227ab45485d3 --- /dev/null +++ b/src/rgen/Microsoft.Macios.Generator/Emitters/ICodeEmitter.cs @@ -0,0 +1,14 @@ +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis; + +namespace Microsoft.Macios.Generator.Emitters; + +/// +/// Interface to be implemented by all those classes that know how to emit code for a binding. +/// +interface ICodeEmitter { + public string SymbolName {get;} + bool TryValidate ([NotNullWhen (false)] out ImmutableArray? diagnostics); + void Emit (); +} diff --git a/src/rgen/Microsoft.Macios.Generator/Emitters/InterfaceEmitter.cs b/src/rgen/Microsoft.Macios.Generator/Emitters/InterfaceEmitter.cs new file mode 100644 index 000000000000..524fbe89ac3a --- /dev/null +++ b/src/rgen/Microsoft.Macios.Generator/Emitters/InterfaceEmitter.cs @@ -0,0 +1,18 @@ +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.Macios.Generator.Context; + +namespace Microsoft.Macios.Generator.Emitters; + +class InterfaceEmitter (ISymbolBindingContext context, TabbedStringBuilder builder) : ICodeEmitter { + public string SymbolName { get; } = string.Empty; + public bool TryValidate ([NotNullWhen(false)] out ImmutableArray? diagnostics) + { + diagnostics = null; + return true; + } + + public void Emit () { } +} diff --git a/src/rgen/Microsoft.Macios.Generator/Microsoft.Macios.Generator.csproj b/src/rgen/Microsoft.Macios.Generator/Microsoft.Macios.Generator.csproj index f9c73ed0396a..7da28f54684d 100644 --- a/src/rgen/Microsoft.Macios.Generator/Microsoft.Macios.Generator.csproj +++ b/src/rgen/Microsoft.Macios.Generator/Microsoft.Macios.Generator.csproj @@ -22,5 +22,20 @@ + + + <_Parameter1>Microsoft.Macios.Generator.Tests + + + + + + external\PlatformName.cs + + + external\PlatformNameExtensions.cs + + + diff --git a/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs b/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs new file mode 100644 index 000000000000..1aa4b5e1cf39 --- /dev/null +++ b/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs @@ -0,0 +1,188 @@ +using System; +using System.Text; + +namespace Microsoft.Macios.Generator; + + +/// +/// String builder wrapper that keeps track of the current indentation level by abusing the IDisposable pattern. Rather +/// than dispose data, what the IDisposable pattern allows is to create a new block with an increased indentation, that +/// way we do not need to keep track of the current indentation level. +/// +/// var classBlock = new TabbedStringBuilder (sb); +/// classBlock.AppendFormatLine ("public static NSString? GetConstant (this {0} self)", enumSymbol.Name); +/// // open a new {} block, no need to keep track of the indentation, the new block has it +/// using (var getConstantBlock = classBlock.CreateBlock (isBlock: true)) { +/// // write the contents of the method here. +/// } +/// +/// +class TabbedStringBuilder : IDisposable { + readonly StringBuilder sb; + readonly uint tabCount; + readonly string tabs; + readonly bool isBlock; + bool disposed; + + /// + /// Created a new tabbed string builder that will use the given sb to write code. + /// + /// The string builder to be used to write code. + /// The original tab size. + /// States if we are creating a {} block. + public TabbedStringBuilder (StringBuilder builder, uint currentCount = 0, bool block = false) + { + sb = builder; + isBlock = block; + if (isBlock) { + // increase by 1 because we are in a block + tabCount = currentCount + 1; + var braceTab = new string ('\t', (int) (tabCount - 1)); + this.sb.AppendLine ($"{braceTab}{{"); + } else { + tabCount = currentCount; + } + tabs = new string ('\t', (int) tabCount); + } + + /// + /// Append a new empty line to the string builder using the correct tab size. + /// + /// The current tabbed string builder. + public TabbedStringBuilder AppendLine () + { + sb.AppendLine (); + return this; + } + + /// + /// Append a line, but do not add a \n + /// + /// + /// + public TabbedStringBuilder Append (string line) + { + if (string.IsNullOrWhiteSpace (line)) { + sb.Append (line); + } else { + sb.Append ($"{tabs}{line}"); + } + return this; + } + + /// + /// Append a new tabbed line. + /// + /// The line to append. + /// The current builder. + public TabbedStringBuilder AppendLine (string line) + { + if (string.IsNullOrWhiteSpace (line)) { + sb.AppendLine (line); + } else { + sb.AppendLine ($"{tabs}{line}"); + } + return this; + } + + public TabbedStringBuilder AppendFormatLine (string format, params object [] args) + { + sb.AppendFormat ($"{tabs}{format}", args); + sb.AppendLine (); + return this; + } + + /// + /// Append a new raw literal by prepending the correct indentation. + /// + /// The raw string to append. + /// The current builder. + public TabbedStringBuilder AppendRaw (string rawString) + { + // we will split the raw string in lines and then append them so that the + // tabbing is correct + var lines = rawString.Split (['\n'], StringSplitOptions.None); + for (var index = 0; index < lines.Length; index++) { + var line = lines [index]; + if (index == lines.Length - 1) { + Append (line); + } else { + AppendLine (line); + } + } + return this; + } + + /// + /// Append the generated code attribute to the current string builder. Added for convenience. + /// + /// If the binding is Optimizable or not. + /// The current builder. + public TabbedStringBuilder AppendGeneratedCodeAttribute (bool optimizable = true) + { + if (optimizable) { + const string attr = "[BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]"; + AppendLine (attr); + } else { + const string attr = "[BindingImpl (BindingImplOptions.GeneratedCode)]"; + AppendLine (attr); + } + + return this; + } + + /// + /// Append a EditorBrowsable attribute. Added for convenience. + /// + /// The current builder. + public TabbedStringBuilder AppendEditorBrowsableAttribute () + { + const string attr = "[EditorBrowsable (EditorBrowsableState.Never)]"; + AppendLine (attr); + return this; + } + + /// + /// Create a bew empty block. + /// + /// If it is a block that uses {} or not. + /// The new bloc. + public TabbedStringBuilder CreateBlock (bool block) => CreateBlock (string.Empty, block); + + /// + /// Create a new block with the given line. This method can be used to write if/else statements. + /// + /// The new line to append + /// If the new line should considered a block. + /// The current builder. + public TabbedStringBuilder CreateBlock (string line, bool block) + { + if (!string.IsNullOrEmpty (line)) { + sb.AppendLine ($"{tabs}{line}"); + } + + return new TabbedStringBuilder (sb, tabCount, block); + } + + /// + /// Return the string builder as a string. + /// + /// + public override string ToString () + { + Dispose (); + return sb.ToString (); + } + + /// + /// Does not really dispose anything, it just closes the current block. + /// + public void Dispose () + { + if (disposed || !isBlock) return; + + var braceTab = new string ('\t', (int)(tabCount - 1)); + disposed = true; + sb.AppendLine ($"{braceTab}}}"); + } +} diff --git a/tests/generator/PlatformNameExtensionsTests.cs b/tests/generator/PlatformNameExtensionsTests.cs index e955c69ab010..6daef60a5042 100644 --- a/tests/generator/PlatformNameExtensionsTests.cs +++ b/tests/generator/PlatformNameExtensionsTests.cs @@ -17,7 +17,10 @@ public class PlatformNameExtensions { [TestCase (PlatformName.MacCatalyst, "UIApplication")] [TestCase (PlatformName.MacOSX, "NSApplication")] public void GetApplicationClassNameTest (PlatformName platformName, string expected) - => Assert.AreEqual (expected, platformName.GetApplicationClassName ()); + { + Assert.True (platformName.TryGetApplicationClassName (out var applicationClassName)); + Assert.AreEqual (expected, applicationClassName); + } [TestCase (PlatformName.MacOSX, 2)] [TestCase (PlatformName.iOS, 2)] diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/BindingSourceGeneratorGeneratorTests.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/BindingSourceGeneratorGeneratorTests.cs index a101ccbbd214..d9e1e6e39774 100644 --- a/tests/rgen/Microsoft.Macios.Generator.Tests/BindingSourceGeneratorGeneratorTests.cs +++ b/tests/rgen/Microsoft.Macios.Generator.Tests/BindingSourceGeneratorGeneratorTests.cs @@ -1,5 +1,4 @@ using System.Linq; -using Microsoft.CodeAnalysis.CSharp; using Xamarin.Tests; using Xamarin.Utils; using Xunit; @@ -9,36 +8,51 @@ namespace Microsoft.Macios.Generator.Tests; // Unit test that ensures that all the generator attributes are correctly added in the compilation initialization public class BindingSourceGeneratorGeneratorTests : BaseGeneratorTestClass { - const string SampleBindingType = @" + const string usingImportInput = @" +using System; +using Foundation; +using ObjCBindings; namespace TestNamespace; [BindingType (Name = ""AVAudioPCMBuffer"")] -interface AVAudioPcmBuffer : AVAudioBuffer { +public partial class AVAudioPcmBuffer : AVAudioBuffer { +} +"; + + const string usingImportOutput = @"// +using System; +using Foundation; +using ObjCBindings; + +#nullable enable + +namespace TestNamespace +{ + public partial class AVAudioPcmBuffer + { + // TODO: add binding code here + } } "; [Theory] - [PlatformInlineData (ApplePlatform.iOS)] - [PlatformInlineData (ApplePlatform.TVOS)] - [PlatformInlineData (ApplePlatform.MacOSX)] - [PlatformInlineData (ApplePlatform.MacCatalyst)] - public void AttributesAreNotPresent (ApplePlatform platform) + [PlatformInlineData (ApplePlatform.iOS, usingImportInput, usingImportOutput)] + [PlatformInlineData (ApplePlatform.TVOS, usingImportInput, usingImportOutput)] + [PlatformInlineData (ApplePlatform.MacOSX, usingImportInput, usingImportOutput)] + [PlatformInlineData (ApplePlatform.MacCatalyst, usingImportInput, usingImportOutput)] + public void CorrectUsingImports (ApplePlatform platform, string input, string expectedOutput) { // We need to create a compilation with the required source code. - var compilation = CreateCompilation (nameof (AttributesAreNotPresent), - platform, SampleBindingType); + var compilation = CreateCompilation (nameof (CorrectUsingImports), + platform, usingImportInput); // Run generators and retrieve all results. var runResult = _driver.RunGenerators (compilation).GetRunResult (); + Assert.Empty (runResult.Diagnostics); // ensure that we do have all the needed attributes present - var expectedGeneratedAttributes = new [] { - "BindingTypeAttribute.g.cs", - }; - - foreach (string generatedAttribute in expectedGeneratedAttributes) { - var generatedFile = runResult.GeneratedTrees.SingleOrDefault (t => t.FilePath.EndsWith (generatedAttribute)); - Assert.Null (generatedFile); - } + var generatedFile = runResult.GeneratedTrees.SingleOrDefault (t => t.FilePath.EndsWith ("AVAudioPcmBuffer.g.cs")); + Assert.NotNull (generatedFile); + Assert.Equal (expectedOutput, generatedFile.GetText ().ToString ()); } } diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/TabbedStringBuilderTests.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/TabbedStringBuilderTests.cs new file mode 100644 index 000000000000..c7ccf7a460c8 --- /dev/null +++ b/tests/rgen/Microsoft.Macios.Generator.Tests/TabbedStringBuilderTests.cs @@ -0,0 +1,205 @@ +using System.Text; +using Xunit; + +namespace Microsoft.Macios.Generator.Tests; + +public class TabbedStringBuilderTests { + StringBuilder sb; + + public TabbedStringBuilderTests () + { + sb = new(); + } + + [Theory] + [InlineData (0, "")] + [InlineData (1, "\t")] + [InlineData (5, "\t\t\t\t\t")] + public void ConstructorNotBlockTest (uint tabCount, string expectedTabs) + { + string result; + using (var block = new TabbedStringBuilder (sb, tabCount)) { + block.AppendLine ("Test"); + result = block.ToString (); + } + Assert.Equal ($"{expectedTabs}Test\n", result); + } + + [Theory] + [InlineData (0, "")] + [InlineData (1, "\t")] + [InlineData (5, "\t\t\t\t\t")] + public void ConstructorBlockTest (uint tabCount, string expectedTabs) + { + string result; + using (var block = new TabbedStringBuilder (sb, tabCount, true)) { + block.AppendLine ("Test"); + result = block.ToString (); + } + Assert.Equal ($"{expectedTabs}{{\n{expectedTabs}\tTest\n{expectedTabs}}}\n", result); + } + + [Theory] + [InlineData (0)] + [InlineData (1)] + [InlineData (5)] + public void AppendLineTest (uint tabCount) + { + string result; + using (var block = new TabbedStringBuilder (sb, tabCount)) { + block.AppendLine (); + result = block.ToString (); + } + // an empty line should have not tabs + Assert.Equal ("\n", result); + } + + [Theory] + [InlineData ("// test comment", 0, "")] + [InlineData ("var t = 1;", 1, "\t")] + [InlineData ("Console.WriteLine (\"1\");", 5, "\t\t\t\t\t")] + public void AppendLineStringTest (string line, uint tabCount, string expectedTabs) + { + string result; + using (var block = new TabbedStringBuilder (sb, tabCount, true)) { + block.AppendLine (line); + result = block.ToString (); + } + Assert.Equal ($"{expectedTabs}{{\n{expectedTabs}\t{line}\n{expectedTabs}}}\n", result); + } + + [Theory] + [InlineData ("// this is a string {0}", "example", 0, "")] + [InlineData ("var t = {0};", 1, 1, "\t")] + [InlineData ("Console.WriteLine (\"{0}\")", "Hello", 5, "\t\t\t\t\t")] + public void AppendFormatLineTest (string format, object formatParam, uint tabCount, string expectedTabs) + { + var line = string.Format (format, formatParam); + var expected = $"{expectedTabs}{{\n{expectedTabs}\t{line}\n{expectedTabs}}}\n"; + string result; + using (var block = new TabbedStringBuilder (sb, tabCount, true)) { + block.AppendFormatLine(format, formatParam); + result = block.ToString (); + } + Assert.Equal (expected,result); + } + + [Theory] + [InlineData (0, "")] + [InlineData (1, "\t")] + [InlineData (5, "\t\t\t\t\t")] + public void AppendRawTest (uint tabCount, string expectedTabs) + { + var input = @" +## Raw string +Because we are using a raw string we expected: + 1. The string to be split in lines + 2. All lines should have the right indentation + - This means nested one + 3. And all lines should have the correct tabs +"; + var expected = $@" +{expectedTabs}## Raw string +{expectedTabs}Because we are using a raw string we expected: +{expectedTabs} 1. The string to be split in lines +{expectedTabs} 2. All lines should have the right indentation +{expectedTabs} - This means nested one +{expectedTabs} 3. And all lines should have the correct tabs +"; + string result; + using (var block = new TabbedStringBuilder (sb, tabCount)) { + block.AppendRaw (input); + result = block.ToString (); + } + Assert.Equal (expected, result); + } + + [Theory] + [InlineData (0, "")] + [InlineData (1, "\t")] + [InlineData (5, "\t\t\t\t\t")] + public void AppendGeneratedCodeAttributeTest (uint tabCount, string expectedTabs) + { + var expected = $"{expectedTabs}[BindingImpl (BindingImplOptions.GeneratedCode)]\n"; + string result; + using (var block = new TabbedStringBuilder (sb, tabCount)) { + block.AppendGeneratedCodeAttribute (false); + result = block.ToString (); + } + Assert.Equal (expected, result); + } + + [Theory] + [InlineData (0, "")] + [InlineData (1, "\t")] + [InlineData (5, "\t\t\t\t\t")] + public void AppendGeneratedCodeAttributeOptimizableTest (uint tabCount, string expectedTabs) + { + var expected = $"{expectedTabs}[BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]\n"; + string result; + using (var block = new TabbedStringBuilder (sb, tabCount)) { + block.AppendGeneratedCodeAttribute (true); + result = block.ToString (); + } + Assert.Equal (expected, result); + } + + [Theory] + [InlineData (0, "")] + [InlineData (1, "\t")] + [InlineData (5, "\t\t\t\t\t")] + public void AppendEditorBrowsableAttributeTest (uint tabCount, string expectedTabs) + { + var expected = $"{expectedTabs}[EditorBrowsable (EditorBrowsableState.Never)]\n"; + string result; + using (var block = new TabbedStringBuilder (sb, tabCount)) { + block.AppendEditorBrowsableAttribute (); + result = block.ToString (); + } + Assert.Equal (expected, result); + } + + [Theory] + [InlineData (0, "")] + [InlineData (1, "\t")] + [InlineData (5, "\t\t\t\t\t")] + public void CreateEmptyBlockTest (uint tabCount, string expectedTabs) + { + var blockContent = "// the test"; + var expected = $@"{expectedTabs}{{ +{expectedTabs}{"\t"}{blockContent} +{expectedTabs}}} +"; + string result; + using (var block = new TabbedStringBuilder (sb, tabCount)) { + using (var nested = block.CreateBlock (true)) { + nested.AppendLine (blockContent); + } + result = block.ToString (); + } + Assert.Equal (expected, result); + } + + [Theory] + [InlineData (0, "", "if (true)")] + [InlineData (1, "\t", "using (var t = new StringBuilder)")] + [InlineData (5, "\t\t\t\t\t", "fixed (*foo)")] + public void CreateBlockTest (uint tabCount, string expectedTabs, string blockType) + { + var blockContent = "// the test"; + var expected = $@"{expectedTabs}{blockType} +{expectedTabs}{{ +{expectedTabs}{"\t"}{blockContent} +{expectedTabs}}} +"; + string result; + using (var block = new TabbedStringBuilder (sb, tabCount)) { + using (var nested = block.CreateBlock (blockType, true)) { + nested.AppendLine (blockContent); + } + result = block.ToString (); + } + Assert.Equal (expected, result); + } + +} From 1ac897444eaa702f09602c2e63a27f8171d1ffb7 Mon Sep 17 00:00:00 2001 From: GitHub Actions Autoformatter Date: Wed, 16 Oct 2024 17:27:31 +0000 Subject: [PATCH 02/22] Auto-format source code --- src/bgen/Extensions/PlatformNameExtensions.cs | 8 +++--- .../Extensions/PlatformNameExtensionsBgen.cs | 2 +- .../BindingSourceGeneratorGenerator.cs | 26 +++++++++---------- .../Context/ContextFactory.cs | 2 +- .../Context/ISymbolBindingContext.cs | 3 +-- .../Context/RootBindingContext.cs | 4 +-- .../Context/SymbolBindingContext.cs | 8 +++--- .../Emitters/ClassEmitter.cs | 5 ++-- .../Emitters/EmitterFactory.cs | 2 +- .../Emitters/EnumEmitter.cs | 2 +- .../Emitters/ICodeEmitter.cs | 2 +- .../Emitters/InterfaceEmitter.cs | 2 +- .../TabbedStringBuilder.cs | 2 +- .../TabbedStringBuilderTests.cs | 6 ++--- 14 files changed, 36 insertions(+), 38 deletions(-) diff --git a/src/bgen/Extensions/PlatformNameExtensions.cs b/src/bgen/Extensions/PlatformNameExtensions.cs index f78d318c9954..ea0b3914cac9 100644 --- a/src/bgen/Extensions/PlatformNameExtensions.cs +++ b/src/bgen/Extensions/PlatformNameExtensions.cs @@ -2,7 +2,7 @@ public static class PlatformNameExtensions { - public static bool TryGetApplicationClassName (this PlatformName currentPlatform, [NotNullWhen(true)]out string? className) + public static bool TryGetApplicationClassName (this PlatformName currentPlatform, [NotNullWhen (true)] out string? className) { switch (currentPlatform) { case PlatformName.iOS: @@ -20,7 +20,7 @@ public static bool TryGetApplicationClassName (this PlatformName currentPlatform } } - public static bool TryGetCoreImageMap (this PlatformName currentPlatform, [NotNullWhen(true)]out string? coreImageMap) + public static bool TryGetCoreImageMap (this PlatformName currentPlatform, [NotNullWhen (true)] out string? coreImageMap) { switch (currentPlatform) { case PlatformName.iOS: @@ -38,7 +38,7 @@ public static bool TryGetCoreImageMap (this PlatformName currentPlatform, [NotNu } } - public static bool TryGetCoreServicesMap (this PlatformName currentPlatform, [NotNullWhen(true)]out string? coreServicesMap) + public static bool TryGetCoreServicesMap (this PlatformName currentPlatform, [NotNullWhen (true)] out string? coreServicesMap) { switch (currentPlatform) { case PlatformName.iOS: @@ -56,7 +56,7 @@ public static bool TryGetCoreServicesMap (this PlatformName currentPlatform, [No } } - public static bool TryGetPDFKitMap (this PlatformName currentPlatform, [NotNullWhen(true)]out string? pdfKitMap) + public static bool TryGetPDFKitMap (this PlatformName currentPlatform, [NotNullWhen (true)] out string? pdfKitMap) { switch (currentPlatform) { case PlatformName.iOS: diff --git a/src/bgen/Extensions/PlatformNameExtensionsBgen.cs b/src/bgen/Extensions/PlatformNameExtensionsBgen.cs index 5c9d90d9c20a..45e1a29ba443 100644 --- a/src/bgen/Extensions/PlatformNameExtensionsBgen.cs +++ b/src/bgen/Extensions/PlatformNameExtensionsBgen.cs @@ -21,7 +21,7 @@ public static string GetCoreImageMap (this PlatformName currentPlatform) public static string GetCoreServicesMap (this PlatformName currentPlatform) { - if (currentPlatform.TryGetCoreServicesMap(out var coreServicesMap)) + if (currentPlatform.TryGetCoreServicesMap (out var coreServicesMap)) return coreServicesMap; throw new BindingException (1047, currentPlatform); } diff --git a/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs b/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs index 4a7e286aaebf..807d2fda4302 100644 --- a/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs +++ b/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs @@ -43,7 +43,7 @@ public void Initialize (IncrementalGeneratorInitializationContext context) /// /// The compilation context /// The base type declaration that we are going to generate. - static void AddPipeline (IncrementalGeneratorInitializationContext context) where T: BaseTypeDeclarationSyntax + static void AddPipeline (IncrementalGeneratorInitializationContext context) where T : BaseTypeDeclarationSyntax { var provider = context.SyntaxProvider .CreateSyntaxProvider ( @@ -71,16 +71,16 @@ static void AddPipeline (IncrementalGeneratorInitializationContext context) w // Go through all attributes of the class. foreach (AttributeListSyntax attributeListSyntax in classDeclarationSyntax.AttributeLists) - foreach (AttributeSyntax attributeSyntax in attributeListSyntax.Attributes) { - if (context.SemanticModel.GetSymbolInfo (attributeSyntax).Symbol is not IMethodSymbol attributeSymbol) - continue; // if we can't get the symbol, ignore it + foreach (AttributeSyntax attributeSyntax in attributeListSyntax.Attributes) { + if (context.SemanticModel.GetSymbolInfo (attributeSyntax).Symbol is not IMethodSymbol attributeSymbol) + continue; // if we can't get the symbol, ignore it - string attributeName = attributeSymbol.ContainingType.ToDisplayString (); + string attributeName = attributeSymbol.ContainingType.ToDisplayString (); - // Check the full name of the [Binding] attribute. - if (attributeName == AttributesNames.BindingAttribute) - return (classDeclarationSyntax, true); - } + // Check the full name of the [Binding] attribute. + if (attributeName == AttributesNames.BindingAttribute) + return (classDeclarationSyntax, true); + } return (classDeclarationSyntax, false); } @@ -119,7 +119,7 @@ static void CollectUsingStatements (SyntaxTree tree, TabbedStringBuilder sb) /// The compilation unit. /// The base type declarations marked by the BindingTypeAttribute. /// The type of type declaration. - static void GenerateCode (SourceProductionContext context, Compilation compilation, + static void GenerateCode (SourceProductionContext context, Compilation compilation, ImmutableArray baseTypeDeclarations) where T : BaseTypeDeclarationSyntax { var rootContext = new RootBindingContext (compilation); @@ -129,8 +129,8 @@ static void CollectUsingStatements (SyntaxTree tree, TabbedStringBuilder sb) continue; // init sb and add all the using statements from the base type declaration - var sb = new TabbedStringBuilder (new()); - sb.AppendLine("// "); + var sb = new TabbedStringBuilder (new ()); + sb.AppendLine ("// "); CollectUsingStatements (baseTypeDeclarationSyntax.SyntaxTree, sb); // enable nullable! @@ -141,7 +141,7 @@ static void CollectUsingStatements (SyntaxTree tree, TabbedStringBuilder sb) // delegate semantic model and syntax tree analysis to the emitter who will generate the code and knows // best if (ContextFactory.TryCreate (rootContext, semanticModel, namedTypeSymbol, baseTypeDeclarationSyntax, out var symbolBindingContext) - && EmitterFactory.TryCreate (symbolBindingContext, sb, out var emitter)) { + && EmitterFactory.TryCreate (symbolBindingContext, sb, out var emitter)) { if (!emitter.TryValidate (out var diagnostics)) { continue; } diff --git a/src/rgen/Microsoft.Macios.Generator/Context/ContextFactory.cs b/src/rgen/Microsoft.Macios.Generator/Context/ContextFactory.cs index 98ad17c9fc00..0805f661df89 100644 --- a/src/rgen/Microsoft.Macios.Generator/Context/ContextFactory.cs +++ b/src/rgen/Microsoft.Macios.Generator/Context/ContextFactory.cs @@ -8,7 +8,7 @@ namespace Microsoft.Macios.Generator.Context; static class ContextFactory { public static bool TryCreate (RootBindingContext context, SemanticModel semanticModel, - INamedTypeSymbol symbol, T declarationSyntax, [NotNullWhen(true)] out ISymbolBindingContext? bindingContext) where T : BaseTypeDeclarationSyntax + INamedTypeSymbol symbol, T declarationSyntax, [NotNullWhen (true)] out ISymbolBindingContext? bindingContext) where T : BaseTypeDeclarationSyntax { bindingContext = declarationSyntax switch { ClassDeclarationSyntax c => Unsafe.As> ( diff --git a/src/rgen/Microsoft.Macios.Generator/Context/ISymbolBindingContext.cs b/src/rgen/Microsoft.Macios.Generator/Context/ISymbolBindingContext.cs index c4e293fbac7f..a9bc1f3c242f 100644 --- a/src/rgen/Microsoft.Macios.Generator/Context/ISymbolBindingContext.cs +++ b/src/rgen/Microsoft.Macios.Generator/Context/ISymbolBindingContext.cs @@ -8,8 +8,7 @@ namespace Microsoft.Macios.Generator.Context; /// because it removes the need to a cast. /// /// The base type declaration whose context we have -interface ISymbolBindingContext where T : BaseTypeDeclarationSyntax -{ +interface ISymbolBindingContext where T : BaseTypeDeclarationSyntax { T DeclarationSyntax { get; } string Namespace { get; } string SymbolName { get; } diff --git a/src/rgen/Microsoft.Macios.Generator/Context/RootBindingContext.cs b/src/rgen/Microsoft.Macios.Generator/Context/RootBindingContext.cs index 28a1b8caf5b2..81ae4f3e8544 100644 --- a/src/rgen/Microsoft.Macios.Generator/Context/RootBindingContext.cs +++ b/src/rgen/Microsoft.Macios.Generator/Context/RootBindingContext.cs @@ -13,7 +13,7 @@ namespace Microsoft.Macios.Generator.Context; /// to the current compilation. /// class RootBindingContext { - readonly Dictionary _libraries = new(); + readonly Dictionary _libraries = new (); public PlatformName CurrentPlatform { get; set; } public Compilation Compilation { get; set; } @@ -65,7 +65,7 @@ public bool TryComputeLibraryName (string attributeLibraryName, string typeNames break; case "+PDFKit": libraryName = "PdfKit"; - CurrentPlatform.TryGetPDFKitMap (out libraryPath); + CurrentPlatform.TryGetPDFKitMap (out libraryPath); break; } } else { diff --git a/src/rgen/Microsoft.Macios.Generator/Context/SymbolBindingContext.cs b/src/rgen/Microsoft.Macios.Generator/Context/SymbolBindingContext.cs index 1750ed728a81..b3d0c24cfbb8 100644 --- a/src/rgen/Microsoft.Macios.Generator/Context/SymbolBindingContext.cs +++ b/src/rgen/Microsoft.Macios.Generator/Context/SymbolBindingContext.cs @@ -11,7 +11,7 @@ class SymbolBindingContext { public bool IsStatic => Symbol.IsStatic; - public SymbolBindingContext(RootBindingContext rootBindingContext, + public SymbolBindingContext (RootBindingContext rootBindingContext, SemanticModel semanticModel, INamedTypeSymbol symbol) { RootBindingContext = rootBindingContext; @@ -24,12 +24,12 @@ public SymbolBindingContext(RootBindingContext rootBindingContext, class SymbolBindingContext : SymbolBindingContext, ISymbolBindingContext where T : BaseTypeDeclarationSyntax { public T DeclarationSyntax { get; } - public string Namespace => Symbol.ContainingNamespace.ToDisplayString(); + public string Namespace => Symbol.ContainingNamespace.ToDisplayString (); public string SymbolName => Symbol.Name; - public SymbolBindingContext(RootBindingContext rootBindingContext, + public SymbolBindingContext (RootBindingContext rootBindingContext, SemanticModel semanticModel, INamedTypeSymbol symbol, T declarationSyntax) - : base(rootBindingContext, semanticModel, symbol) + : base (rootBindingContext, semanticModel, symbol) { DeclarationSyntax = declarationSyntax; } diff --git a/src/rgen/Microsoft.Macios.Generator/Emitters/ClassEmitter.cs b/src/rgen/Microsoft.Macios.Generator/Emitters/ClassEmitter.cs index 023ee9b6922b..de3db38ff478 100644 --- a/src/rgen/Microsoft.Macios.Generator/Emitters/ClassEmitter.cs +++ b/src/rgen/Microsoft.Macios.Generator/Emitters/ClassEmitter.cs @@ -18,9 +18,8 @@ public void Emit () { // add the namespace and the class declaration using (var namespaceBlock = builder.CreateBlock ($"namespace {context.Namespace}", true)) { - using (var classBlock = namespaceBlock.CreateBlock ($"public partial class {SymbolName}", true)) - { - classBlock.AppendLine("// TODO: add binding code here"); + using (var classBlock = namespaceBlock.CreateBlock ($"public partial class {SymbolName}", true)) { + classBlock.AppendLine ("// TODO: add binding code here"); } } } diff --git a/src/rgen/Microsoft.Macios.Generator/Emitters/EmitterFactory.cs b/src/rgen/Microsoft.Macios.Generator/Emitters/EmitterFactory.cs index 0f2f351a7bff..7d9237ce35b5 100644 --- a/src/rgen/Microsoft.Macios.Generator/Emitters/EmitterFactory.cs +++ b/src/rgen/Microsoft.Macios.Generator/Emitters/EmitterFactory.cs @@ -9,7 +9,7 @@ namespace Microsoft.Macios.Generator.Emitters; /// static class EmitterFactory { public static bool TryCreate (ISymbolBindingContext context, TabbedStringBuilder builder, - [NotNullWhen(true)]out ICodeEmitter? emitter) + [NotNullWhen (true)] out ICodeEmitter? emitter) where T : BaseTypeDeclarationSyntax { emitter = context switch { diff --git a/src/rgen/Microsoft.Macios.Generator/Emitters/EnumEmitter.cs b/src/rgen/Microsoft.Macios.Generator/Emitters/EnumEmitter.cs index 2f1910947554..a2bffb807838 100644 --- a/src/rgen/Microsoft.Macios.Generator/Emitters/EnumEmitter.cs +++ b/src/rgen/Microsoft.Macios.Generator/Emitters/EnumEmitter.cs @@ -11,7 +11,7 @@ class EnumEmitter (ISymbolBindingContext context, TabbedS public string SymbolName => $"{context.SymbolName}Extensions"; - public bool TryValidate ([NotNullWhen(false)] out ImmutableArray? diagnostics) + public bool TryValidate ([NotNullWhen (false)] out ImmutableArray? diagnostics) { diagnostics = null; return true; diff --git a/src/rgen/Microsoft.Macios.Generator/Emitters/ICodeEmitter.cs b/src/rgen/Microsoft.Macios.Generator/Emitters/ICodeEmitter.cs index 227ab45485d3..d0706b95f92a 100644 --- a/src/rgen/Microsoft.Macios.Generator/Emitters/ICodeEmitter.cs +++ b/src/rgen/Microsoft.Macios.Generator/Emitters/ICodeEmitter.cs @@ -8,7 +8,7 @@ namespace Microsoft.Macios.Generator.Emitters; /// Interface to be implemented by all those classes that know how to emit code for a binding. /// interface ICodeEmitter { - public string SymbolName {get;} + public string SymbolName { get; } bool TryValidate ([NotNullWhen (false)] out ImmutableArray? diagnostics); void Emit (); } diff --git a/src/rgen/Microsoft.Macios.Generator/Emitters/InterfaceEmitter.cs b/src/rgen/Microsoft.Macios.Generator/Emitters/InterfaceEmitter.cs index 524fbe89ac3a..baacb3d39b31 100644 --- a/src/rgen/Microsoft.Macios.Generator/Emitters/InterfaceEmitter.cs +++ b/src/rgen/Microsoft.Macios.Generator/Emitters/InterfaceEmitter.cs @@ -8,7 +8,7 @@ namespace Microsoft.Macios.Generator.Emitters; class InterfaceEmitter (ISymbolBindingContext context, TabbedStringBuilder builder) : ICodeEmitter { public string SymbolName { get; } = string.Empty; - public bool TryValidate ([NotNullWhen(false)] out ImmutableArray? diagnostics) + public bool TryValidate ([NotNullWhen (false)] out ImmutableArray? diagnostics) { diagnostics = null; return true; diff --git a/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs b/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs index 1aa4b5e1cf39..266aeba542d2 100644 --- a/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs +++ b/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs @@ -181,7 +181,7 @@ public void Dispose () { if (disposed || !isBlock) return; - var braceTab = new string ('\t', (int)(tabCount - 1)); + var braceTab = new string ('\t', (int) (tabCount - 1)); disposed = true; sb.AppendLine ($"{braceTab}}}"); } diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/TabbedStringBuilderTests.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/TabbedStringBuilderTests.cs index c7ccf7a460c8..5fc924f05523 100644 --- a/tests/rgen/Microsoft.Macios.Generator.Tests/TabbedStringBuilderTests.cs +++ b/tests/rgen/Microsoft.Macios.Generator.Tests/TabbedStringBuilderTests.cs @@ -8,7 +8,7 @@ public class TabbedStringBuilderTests { public TabbedStringBuilderTests () { - sb = new(); + sb = new (); } [Theory] @@ -78,10 +78,10 @@ public void AppendFormatLineTest (string format, object formatParam, uint tabCou var expected = $"{expectedTabs}{{\n{expectedTabs}\t{line}\n{expectedTabs}}}\n"; string result; using (var block = new TabbedStringBuilder (sb, tabCount, true)) { - block.AppendFormatLine(format, formatParam); + block.AppendFormatLine (format, formatParam); result = block.ToString (); } - Assert.Equal (expected,result); + Assert.Equal (expected, result); } [Theory] From 89aef6230cbe6c73dd39a08a55ae34a84d3f0dec Mon Sep 17 00:00:00 2001 From: Manuel de la Pena Date: Wed, 16 Oct 2024 13:57:58 -0400 Subject: [PATCH 03/22] Fix makefile. --- src/Makefile.generator | 4 ++-- src/Makefile.rgenerator | 3 +++ .../Microsoft.Macios.Generator/Context/RootBindingContext.cs | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Makefile.generator b/src/Makefile.generator index a88c56ec1d50..50f3353b9d12 100644 --- a/src/Makefile.generator +++ b/src/Makefile.generator @@ -70,6 +70,6 @@ $(RSP_DIR)/dotnet/%-defines-dotnet.rsp: frameworks.sources Makefile.generator ge $(Q) ./generate-defines.csharp $@.tmp '$(filter-out $(DOTNET_REMOVED_$(shell echo $* | tr a-z A-Z)_FRAMEWORKS),$($(shell echo $* | tr a-z A-Z)_FRAMEWORKS))' $(Q) mv $@.tmp $@ -$(DOTNET_BUILD_DIR)/Xamarin.Apple.BindingAttributes.dll: bgen/Attributes.cs Makefile.generator | $(DOTNET_BUILD_DIR) - $(Q_DOTNET_BUILD) $(SYSTEM_CSC) $(DOTNET_FLAGS) -out:$@ $< +$(DOTNET_BUILD_DIR)/Xamarin.Apple.BindingAttributes.dll: bgen/Attributes.cs bgen/PlatformName.cs Makefile.generator | $(DOTNET_BUILD_DIR) + $(Q_DOTNET_BUILD) $(SYSTEM_CSC) $(DOTNET_FLAGS) -out:$@ bgen/Attributes.cs bgen/PlatformName.cs diff --git a/src/Makefile.rgenerator b/src/Makefile.rgenerator index e3bd9f2b997a..61c1535594cd 100644 --- a/src/Makefile.rgenerator +++ b/src/Makefile.rgenerator @@ -1,6 +1,9 @@ # Roslyn code generator ROSLYN_GENERATOR=$(DOTNET_BUILD_DIR)/common/rgen/Microsoft.Macios.Generator.dll ROSLYN_GENERATOR_FILES := $(wildcard rgen/Microsoft.Macios.Generator/*.cs) +ROSLYN_GENERATOR_FILES += $(wildcard rgen/Microsoft.Macios.Generator/Context/*.cs) +ROSLYN_GENERATOR_FILES += $(wildcard rgen/Microsoft.Macios.Generator/Emitters/*.cs) +ROSLYN_GENERATOR_FILES += $(wildcard rgen/Microsoft.Macios.Generator/Properties/*.cs) $(ROSLYN_GENERATOR): Makefile.rgenerator $(ROSLYN_GENERATOR_FILES) $(Q_DOTNET_BUILD) $(DOTNET) publish rgen/Microsoft.Macios.Generator/Microsoft.Macios.Generator.csproj $(DOTNET_BUILD_VERBOSITY) /p:Configuration=Debug /p:IntermediateOutputPath=$(abspath $(DOTNET_BUILD_DIR)/IDE/obj/common/rgen)/ /p:OutputPath=$(abspath $(DOTNET_BUILD_DIR)/IDE/bin/common/rgen/)/ diff --git a/src/rgen/Microsoft.Macios.Generator/Context/RootBindingContext.cs b/src/rgen/Microsoft.Macios.Generator/Context/RootBindingContext.cs index 81ae4f3e8544..d175abc70c64 100644 --- a/src/rgen/Microsoft.Macios.Generator/Context/RootBindingContext.cs +++ b/src/rgen/Microsoft.Macios.Generator/Context/RootBindingContext.cs @@ -91,7 +91,7 @@ public bool TryComputeLibraryName (string attributeLibraryName, string typeNames libraryName = typeNamespace; } - if (!_libraries.ContainsKey (libraryName)) + if (libraryName is not null && !_libraries.ContainsKey (libraryName)) _libraries.Add (libraryName, libraryPath); return true; From c8ddb0a7b14aeab89ec5d1e2771eb6186ee6021a Mon Sep 17 00:00:00 2001 From: Manuel de la Pena Date: Wed, 16 Oct 2024 17:14:12 -0400 Subject: [PATCH 04/22] Merge TryValidate and Emit since it makes the API cleaner. --- .../BindingSourceGeneratorGenerator.cs | 15 +++++++++------ .../Emitters/ClassEmitter.cs | 9 +++------ .../Emitters/EnumEmitter.cs | 4 +--- .../Emitters/ICodeEmitter.cs | 3 +-- .../Emitters/InterfaceEmitter.cs | 4 +--- 5 files changed, 15 insertions(+), 20 deletions(-) diff --git a/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs b/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs index 807d2fda4302..a48aab4af396 100644 --- a/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs +++ b/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs @@ -142,14 +142,17 @@ static void GenerateCode (SourceProductionContext context, Compilation compil // best if (ContextFactory.TryCreate (rootContext, semanticModel, namedTypeSymbol, baseTypeDeclarationSyntax, out var symbolBindingContext) && EmitterFactory.TryCreate (symbolBindingContext, sb, out var emitter)) { - if (!emitter.TryValidate (out var diagnostics)) { - continue; + if (emitter.TryEmit(out var diagnostics)) { + // only add file when we do generate code + var code = sb.ToString (); + context.AddSource ($"{emitter.SymbolName}.g.cs", SourceText.From (code, Encoding.UTF8)); + } else { + // add to the diagnostics and continue to the next possible candidate + foreach (Diagnostic diagnostic in diagnostics) { + context.ReportDiagnostic (diagnostic); + } } - emitter.Emit (); - // only add file when we do generate code - var code = sb.ToString (); - context.AddSource ($"{emitter.SymbolName}.g.cs", SourceText.From (code, Encoding.UTF8)); } else { // we don't have a emitter for this type, so we can't generate the code, add a diagnostic letting the // user we do not support what he is trying to do diff --git a/src/rgen/Microsoft.Macios.Generator/Emitters/ClassEmitter.cs b/src/rgen/Microsoft.Macios.Generator/Emitters/ClassEmitter.cs index de3db38ff478..03ec04aef47f 100644 --- a/src/rgen/Microsoft.Macios.Generator/Emitters/ClassEmitter.cs +++ b/src/rgen/Microsoft.Macios.Generator/Emitters/ClassEmitter.cs @@ -8,19 +8,16 @@ namespace Microsoft.Macios.Generator.Emitters; class ClassEmitter (ClassBindingContext context, TabbedStringBuilder builder) : ICodeEmitter { public string SymbolName => context.SymbolName; - public bool TryValidate ([NotNullWhen (false)] out ImmutableArray? diagnostics) + public bool TryEmit ([NotNullWhen (false)] out ImmutableArray? diagnostics) { + diagnostics = null; - return true; - } - - public void Emit () - { // add the namespace and the class declaration using (var namespaceBlock = builder.CreateBlock ($"namespace {context.Namespace}", true)) { using (var classBlock = namespaceBlock.CreateBlock ($"public partial class {SymbolName}", true)) { classBlock.AppendLine ("// TODO: add binding code here"); } } + return true; } } diff --git a/src/rgen/Microsoft.Macios.Generator/Emitters/EnumEmitter.cs b/src/rgen/Microsoft.Macios.Generator/Emitters/EnumEmitter.cs index a2bffb807838..b946986eac9f 100644 --- a/src/rgen/Microsoft.Macios.Generator/Emitters/EnumEmitter.cs +++ b/src/rgen/Microsoft.Macios.Generator/Emitters/EnumEmitter.cs @@ -11,11 +11,9 @@ class EnumEmitter (ISymbolBindingContext context, TabbedS public string SymbolName => $"{context.SymbolName}Extensions"; - public bool TryValidate ([NotNullWhen (false)] out ImmutableArray? diagnostics) + public bool TryEmit ([NotNullWhen (false)] out ImmutableArray? diagnostics) { diagnostics = null; return true; } - - public void Emit () { } } diff --git a/src/rgen/Microsoft.Macios.Generator/Emitters/ICodeEmitter.cs b/src/rgen/Microsoft.Macios.Generator/Emitters/ICodeEmitter.cs index d0706b95f92a..d78584e82bb1 100644 --- a/src/rgen/Microsoft.Macios.Generator/Emitters/ICodeEmitter.cs +++ b/src/rgen/Microsoft.Macios.Generator/Emitters/ICodeEmitter.cs @@ -9,6 +9,5 @@ namespace Microsoft.Macios.Generator.Emitters; /// interface ICodeEmitter { public string SymbolName { get; } - bool TryValidate ([NotNullWhen (false)] out ImmutableArray? diagnostics); - void Emit (); + bool TryEmit ([NotNullWhen (false)] out ImmutableArray? diagnostics); } diff --git a/src/rgen/Microsoft.Macios.Generator/Emitters/InterfaceEmitter.cs b/src/rgen/Microsoft.Macios.Generator/Emitters/InterfaceEmitter.cs index baacb3d39b31..23f77d0c4d76 100644 --- a/src/rgen/Microsoft.Macios.Generator/Emitters/InterfaceEmitter.cs +++ b/src/rgen/Microsoft.Macios.Generator/Emitters/InterfaceEmitter.cs @@ -8,11 +8,9 @@ namespace Microsoft.Macios.Generator.Emitters; class InterfaceEmitter (ISymbolBindingContext context, TabbedStringBuilder builder) : ICodeEmitter { public string SymbolName { get; } = string.Empty; - public bool TryValidate ([NotNullWhen (false)] out ImmutableArray? diagnostics) + public bool TryEmit ([NotNullWhen (false)] out ImmutableArray? diagnostics) { diagnostics = null; return true; } - - public void Emit () { } } From 56c7ed2f201f6b26ac68bab52171a55ca613ba01 Mon Sep 17 00:00:00 2001 From: Manuel de la Pena Date: Wed, 16 Oct 2024 17:16:15 -0400 Subject: [PATCH 05/22] enuable nullable first so that emitters can add extra namespaces. --- .../BindingSourceGeneratorGenerator.cs | 7 +++++-- .../Microsoft.Macios.Generator/Emitters/ClassEmitter.cs | 1 + .../BindingSourceGeneratorGeneratorTests.cs | 5 +++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs b/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs index a48aab4af396..9a6263e4c3f6 100644 --- a/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs +++ b/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs @@ -130,13 +130,16 @@ static void GenerateCode (SourceProductionContext context, Compilation compil // init sb and add all the using statements from the base type declaration var sb = new TabbedStringBuilder (new ()); + // let people know this is generated code sb.AppendLine ("// "); - CollectUsingStatements (baseTypeDeclarationSyntax.SyntaxTree, sb); - + // enable nullable! sb.AppendLine (); sb.AppendFormatLine ("#nullable enable"); sb.AppendLine (); + + CollectUsingStatements (baseTypeDeclarationSyntax.SyntaxTree, sb); + // delegate semantic model and syntax tree analysis to the emitter who will generate the code and knows // best diff --git a/src/rgen/Microsoft.Macios.Generator/Emitters/ClassEmitter.cs b/src/rgen/Microsoft.Macios.Generator/Emitters/ClassEmitter.cs index 03ec04aef47f..a133f1a34530 100644 --- a/src/rgen/Microsoft.Macios.Generator/Emitters/ClassEmitter.cs +++ b/src/rgen/Microsoft.Macios.Generator/Emitters/ClassEmitter.cs @@ -11,6 +11,7 @@ class ClassEmitter (ClassBindingContext context, TabbedStringBuilder builder) : public bool TryEmit ([NotNullWhen (false)] out ImmutableArray? diagnostics) { + builder.AppendLine (); diagnostics = null; // add the namespace and the class declaration using (var namespaceBlock = builder.CreateBlock ($"namespace {context.Namespace}", true)) { diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/BindingSourceGeneratorGeneratorTests.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/BindingSourceGeneratorGeneratorTests.cs index d9e1e6e39774..fd8738e7d3e3 100644 --- a/tests/rgen/Microsoft.Macios.Generator.Tests/BindingSourceGeneratorGeneratorTests.cs +++ b/tests/rgen/Microsoft.Macios.Generator.Tests/BindingSourceGeneratorGeneratorTests.cs @@ -20,12 +20,13 @@ public partial class AVAudioPcmBuffer : AVAudioBuffer { "; const string usingImportOutput = @"// + +#nullable enable + using System; using Foundation; using ObjCBindings; -#nullable enable - namespace TestNamespace { public partial class AVAudioPcmBuffer From ae1b14abeb22c75a4cc740d06dc75a2df20da61d Mon Sep 17 00:00:00 2001 From: Manuel de la Pena Date: Wed, 16 Oct 2024 17:21:08 -0400 Subject: [PATCH 06/22] Fix the sample projects. --- .../Microsoft.Macios.Bindings.Analyzer.Sample/Examples.cs | 2 ++ .../Microsoft.Macios.Bindings.Analyzer.Sample.csproj | 7 +++++++ .../Microsoft.Macios.Generator.Sample.csproj | 7 +++++++ .../Microsoft.Macios.Generator.Sample/SampleBinding.cs | 2 ++ 4 files changed, 18 insertions(+) diff --git a/src/rgen/Microsoft.Macios.Bindings.Analyzer.Sample/Examples.cs b/src/rgen/Microsoft.Macios.Bindings.Analyzer.Sample/Examples.cs index fe4905a77980..16717f554311 100644 --- a/src/rgen/Microsoft.Macios.Bindings.Analyzer.Sample/Examples.cs +++ b/src/rgen/Microsoft.Macios.Bindings.Analyzer.Sample/Examples.cs @@ -1,3 +1,5 @@ +using ObjCBindings; + namespace Microsoft.Macios.Bindings.Analyzer.Sample; // If you don't see warnings, build the Analyzers Project. diff --git a/src/rgen/Microsoft.Macios.Bindings.Analyzer.Sample/Microsoft.Macios.Bindings.Analyzer.Sample.csproj b/src/rgen/Microsoft.Macios.Bindings.Analyzer.Sample/Microsoft.Macios.Bindings.Analyzer.Sample.csproj index bc9520bfbacf..a02eefcf4af5 100644 --- a/src/rgen/Microsoft.Macios.Bindings.Analyzer.Sample/Microsoft.Macios.Bindings.Analyzer.Sample.csproj +++ b/src/rgen/Microsoft.Macios.Bindings.Analyzer.Sample/Microsoft.Macios.Bindings.Analyzer.Sample.csproj @@ -3,6 +3,7 @@ net$(BundledNETCoreAppTargetFrameworkVersion) enable + APL0003 @@ -11,9 +12,15 @@ + + external\BindginTypeAttribute.cs + external\Attributes.cs + + external\PlatformName.cs + diff --git a/src/rgen/Microsoft.Macios.Generator.Sample/Microsoft.Macios.Generator.Sample.csproj b/src/rgen/Microsoft.Macios.Generator.Sample/Microsoft.Macios.Generator.Sample.csproj index f21144e77c4c..3c189f7b610e 100644 --- a/src/rgen/Microsoft.Macios.Generator.Sample/Microsoft.Macios.Generator.Sample.csproj +++ b/src/rgen/Microsoft.Macios.Generator.Sample/Microsoft.Macios.Generator.Sample.csproj @@ -4,6 +4,7 @@ net$(BundledNETCoreAppTargetFrameworkVersion) enable Microsoft.Macios.Generator.Sample + APL0003 @@ -11,9 +12,15 @@ + + external\BindginTypeAttribute.cs + external\Attributes.cs + + external\PlatformName.cs + diff --git a/src/rgen/Microsoft.Macios.Generator.Sample/SampleBinding.cs b/src/rgen/Microsoft.Macios.Generator.Sample/SampleBinding.cs index 28d28329ae21..7c2427c1473e 100644 --- a/src/rgen/Microsoft.Macios.Generator.Sample/SampleBinding.cs +++ b/src/rgen/Microsoft.Macios.Generator.Sample/SampleBinding.cs @@ -1,3 +1,5 @@ +using ObjCBindings; + namespace Microsoft.Macios.Generator.Sample; // This code will not compile until you build the project with the Source Generators From d4fb40e8216ac329b7b500c255080789fc918d2d Mon Sep 17 00:00:00 2001 From: Manuel de la Pena Date: Wed, 16 Oct 2024 17:27:27 -0400 Subject: [PATCH 07/22] Fix analyzer tests. --- .../BindingTypeSemanticAnalyzerTests.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/rgen/Microsoft.Macios.Bindings.Analyzer.Tests/BindingTypeSemanticAnalyzerTests.cs b/tests/rgen/Microsoft.Macios.Bindings.Analyzer.Tests/BindingTypeSemanticAnalyzerTests.cs index 48cba5be3fbd..67c715043299 100644 --- a/tests/rgen/Microsoft.Macios.Bindings.Analyzer.Tests/BindingTypeSemanticAnalyzerTests.cs +++ b/tests/rgen/Microsoft.Macios.Bindings.Analyzer.Tests/BindingTypeSemanticAnalyzerTests.cs @@ -1,3 +1,4 @@ +using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Xamarin.Tests; @@ -27,10 +28,11 @@ public class Examples { var compilation = CreateCompilation (nameof (CompareGeneratedCode), platform, inputText); var diagnostics = await RunAnalyzer (new BindingTypeSemanticAnalyzer (), compilation); - Assert.Single (diagnostics); + var analyzerDiagnotics = diagnostics + .Where (d => d.Id == BindingTypeSemanticAnalyzer.DiagnosticId).ToArray (); + Assert.Single (analyzerDiagnotics); // verify the diagnostic message - var location = diagnostics [0].Location; - VerifyDiagnosticMessage (diagnostics [0], BindingTypeSemanticAnalyzer.DiagnosticId, + VerifyDiagnosticMessage (analyzerDiagnotics [0], BindingTypeSemanticAnalyzer.DiagnosticId, DiagnosticSeverity.Error, "The binding type 'Test.Examples' must declared as a partial class"); } } From ad83462b412dd19c175530fc40a4f6e978f5c2e5 Mon Sep 17 00:00:00 2001 From: GitHub Actions Autoformatter Date: Wed, 16 Oct 2024 21:30:42 +0000 Subject: [PATCH 08/22] Auto-format source code --- .../BindingSourceGeneratorGenerator.cs | 12 ++++++------ .../Emitters/ClassEmitter.cs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs b/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs index 9a6263e4c3f6..91d477a0acbd 100644 --- a/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs +++ b/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs @@ -132,12 +132,12 @@ static void GenerateCode (SourceProductionContext context, Compilation compil var sb = new TabbedStringBuilder (new ()); // let people know this is generated code sb.AppendLine ("// "); - + // enable nullable! sb.AppendLine (); sb.AppendFormatLine ("#nullable enable"); sb.AppendLine (); - + CollectUsingStatements (baseTypeDeclarationSyntax.SyntaxTree, sb); @@ -145,10 +145,10 @@ static void GenerateCode (SourceProductionContext context, Compilation compil // best if (ContextFactory.TryCreate (rootContext, semanticModel, namedTypeSymbol, baseTypeDeclarationSyntax, out var symbolBindingContext) && EmitterFactory.TryCreate (symbolBindingContext, sb, out var emitter)) { - if (emitter.TryEmit(out var diagnostics)) { - // only add file when we do generate code - var code = sb.ToString (); - context.AddSource ($"{emitter.SymbolName}.g.cs", SourceText.From (code, Encoding.UTF8)); + if (emitter.TryEmit (out var diagnostics)) { + // only add file when we do generate code + var code = sb.ToString (); + context.AddSource ($"{emitter.SymbolName}.g.cs", SourceText.From (code, Encoding.UTF8)); } else { // add to the diagnostics and continue to the next possible candidate foreach (Diagnostic diagnostic in diagnostics) { diff --git a/src/rgen/Microsoft.Macios.Generator/Emitters/ClassEmitter.cs b/src/rgen/Microsoft.Macios.Generator/Emitters/ClassEmitter.cs index a133f1a34530..0d56a01a1c10 100644 --- a/src/rgen/Microsoft.Macios.Generator/Emitters/ClassEmitter.cs +++ b/src/rgen/Microsoft.Macios.Generator/Emitters/ClassEmitter.cs @@ -10,7 +10,7 @@ class ClassEmitter (ClassBindingContext context, TabbedStringBuilder builder) : public bool TryEmit ([NotNullWhen (false)] out ImmutableArray? diagnostics) { - + builder.AppendLine (); diagnostics = null; // add the namespace and the class declaration From 7f32f870694d908cc8de4b0f218a019e05e47a46 Mon Sep 17 00:00:00 2001 From: Manuel de la Pena Date: Thu, 17 Oct 2024 11:07:20 -0400 Subject: [PATCH 09/22] Apply suggestions from code review Co-authored-by: Rolf Bjarne Kvinge --- src/Makefile.rgenerator | 4 +--- .../BindingSourceGeneratorGenerator.cs | 4 ++-- .../Microsoft.Macios.Generator/Context/RootBindingContext.cs | 2 +- src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs | 4 ++-- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Makefile.rgenerator b/src/Makefile.rgenerator index 61c1535594cd..61bbed1d2067 100644 --- a/src/Makefile.rgenerator +++ b/src/Makefile.rgenerator @@ -1,9 +1,7 @@ # Roslyn code generator ROSLYN_GENERATOR=$(DOTNET_BUILD_DIR)/common/rgen/Microsoft.Macios.Generator.dll ROSLYN_GENERATOR_FILES := $(wildcard rgen/Microsoft.Macios.Generator/*.cs) -ROSLYN_GENERATOR_FILES += $(wildcard rgen/Microsoft.Macios.Generator/Context/*.cs) -ROSLYN_GENERATOR_FILES += $(wildcard rgen/Microsoft.Macios.Generator/Emitters/*.cs) -ROSLYN_GENERATOR_FILES += $(wildcard rgen/Microsoft.Macios.Generator/Properties/*.cs) +ROSLYN_GENERATOR_FILES += $(wildcard rgen/Microsoft.Macios.Generator/*/*.cs) $(ROSLYN_GENERATOR): Makefile.rgenerator $(ROSLYN_GENERATOR_FILES) $(Q_DOTNET_BUILD) $(DOTNET) publish rgen/Microsoft.Macios.Generator/Microsoft.Macios.Generator.csproj $(DOTNET_BUILD_VERBOSITY) /p:Configuration=Debug /p:IntermediateOutputPath=$(abspath $(DOTNET_BUILD_DIR)/IDE/obj/common/rgen)/ /p:OutputPath=$(abspath $(DOTNET_BUILD_DIR)/IDE/bin/common/rgen/)/ diff --git a/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs b/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs index 91d477a0acbd..562f337fc8e6 100644 --- a/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs +++ b/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs @@ -106,7 +106,7 @@ static void CollectUsingStatements (SyntaxTree tree, TabbedStringBuilder sb) foreach (var ns in usingDirectivesToKeep) { if (string.IsNullOrEmpty (ns)) continue; - sb.AppendFormatLine ("using {0};", ns); + sb.AppendLine ($"using {ns};"); } } @@ -135,7 +135,7 @@ static void GenerateCode (SourceProductionContext context, Compilation compil // enable nullable! sb.AppendLine (); - sb.AppendFormatLine ("#nullable enable"); + sb.AppendLine ("#nullable enable"); sb.AppendLine (); CollectUsingStatements (baseTypeDeclarationSyntax.SyntaxTree, sb); diff --git a/src/rgen/Microsoft.Macios.Generator/Context/RootBindingContext.cs b/src/rgen/Microsoft.Macios.Generator/Context/RootBindingContext.cs index d175abc70c64..08eb2a0390c4 100644 --- a/src/rgen/Microsoft.Macios.Generator/Context/RootBindingContext.cs +++ b/src/rgen/Microsoft.Macios.Generator/Context/RootBindingContext.cs @@ -69,7 +69,7 @@ public bool TryComputeLibraryName (string attributeLibraryName, string typeNames break; } } else { - // we get something in LibraryName from FieldAttribute so we asume + // we get something in LibraryName from FieldAttribute so we assume // it is a path to a library, so we save the path and change library name // to a valid identifier if needed libraryPath = libraryName; diff --git a/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs b/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs index 266aeba542d2..d25a7f3b5a5f 100644 --- a/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs +++ b/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs @@ -181,8 +181,8 @@ public void Dispose () { if (disposed || !isBlock) return; - var braceTab = new string ('\t', (int) (tabCount - 1)); disposed = true; - sb.AppendLine ($"{braceTab}}}"); + sb.Append ('\t', tabCount - 1); + sb.AppendLine (); } } From bca5fcaf5377bb192d13aa08b0b5192159b48e2d Mon Sep 17 00:00:00 2001 From: Manuel de la Pena Date: Thu, 17 Oct 2024 11:14:47 -0400 Subject: [PATCH 10/22] Fix tab usage, add misssing closing brace --- .../TabbedStringBuilder.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs b/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs index d25a7f3b5a5f..73637201a5f3 100644 --- a/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs +++ b/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs @@ -20,7 +20,6 @@ namespace Microsoft.Macios.Generator; class TabbedStringBuilder : IDisposable { readonly StringBuilder sb; readonly uint tabCount; - readonly string tabs; readonly bool isBlock; bool disposed; @@ -42,9 +41,10 @@ public TabbedStringBuilder (StringBuilder builder, uint currentCount = 0, bool b } else { tabCount = currentCount; } - tabs = new string ('\t', (int) tabCount); } + StringBuilder WriteTabs () => sb.Append ('\t', (int) tabCount); + /// /// Append a new empty line to the string builder using the correct tab size. /// @@ -65,7 +65,7 @@ public TabbedStringBuilder Append (string line) if (string.IsNullOrWhiteSpace (line)) { sb.Append (line); } else { - sb.Append ($"{tabs}{line}"); + WriteTabs ().Append (line); } return this; } @@ -80,14 +80,14 @@ public TabbedStringBuilder AppendLine (string line) if (string.IsNullOrWhiteSpace (line)) { sb.AppendLine (line); } else { - sb.AppendLine ($"{tabs}{line}"); + WriteTabs ().AppendLine (line); } return this; } public TabbedStringBuilder AppendFormatLine (string format, params object [] args) { - sb.AppendFormat ($"{tabs}{format}", args); + WriteTabs().AppendFormat (format, args); sb.AppendLine (); return this; } @@ -158,7 +158,7 @@ public TabbedStringBuilder AppendEditorBrowsableAttribute () public TabbedStringBuilder CreateBlock (string line, bool block) { if (!string.IsNullOrEmpty (line)) { - sb.AppendLine ($"{tabs}{line}"); + WriteTabs ().AppendLine (line); } return new TabbedStringBuilder (sb, tabCount, block); @@ -182,7 +182,7 @@ public void Dispose () if (disposed || !isBlock) return; disposed = true; - sb.Append ('\t', tabCount - 1); - sb.AppendLine (); + sb.Append ('\t', (int) tabCount - 1); + sb.AppendLine ("}"); } } From 5f90c0e495720d64b877abcea71774a5897a31b9 Mon Sep 17 00:00:00 2001 From: GitHub Actions Autoformatter Date: Thu, 17 Oct 2024 15:18:30 +0000 Subject: [PATCH 11/22] Auto-format source code --- src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs b/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs index 73637201a5f3..5a60eeaeb34b 100644 --- a/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs +++ b/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs @@ -87,7 +87,7 @@ public TabbedStringBuilder AppendLine (string line) public TabbedStringBuilder AppendFormatLine (string format, params object [] args) { - WriteTabs().AppendFormat (format, args); + WriteTabs ().AppendFormat (format, args); sb.AppendLine (); return this; } From d95ed589a21ff990e2103b7926cb5316653836b6 Mon Sep 17 00:00:00 2001 From: Manuel de la Pena Date: Thu, 17 Oct 2024 14:22:26 -0400 Subject: [PATCH 12/22] Add AppendRaw implementation with fewer string allocations. --- .../TabbedStringBuilder.cs | 49 ++++++++++++++++--- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs b/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs index 73637201a5f3..145ba478f498 100644 --- a/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs +++ b/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs @@ -56,10 +56,10 @@ public TabbedStringBuilder AppendLine () } /// - /// Append a line, but do not add a \n + /// Append conteng, but do not add a \n /// - /// - /// + /// The content to append. + /// The current builder. public TabbedStringBuilder Append (string line) { if (string.IsNullOrWhiteSpace (line)) { @@ -70,6 +70,22 @@ public TabbedStringBuilder Append (string line) return this; } + /// + /// Append conteng, but do not add a \n + /// + /// The content to append. + /// The current builder. + public TabbedStringBuilder Append (ReadOnlySpan span) + { + if (span.IsWhiteSpace ()) { + sb.Append (span); + } else { + WriteTabs ().Append (span); + } + + return this; + } + /// /// Append a new tabbed line. /// @@ -85,6 +101,22 @@ public TabbedStringBuilder AppendLine (string line) return this; } + /// + /// Append a new tabbed lien from the span. + /// + /// The line to append. + /// The current builder. + public TabbedStringBuilder AppendLine (ReadOnlySpan span) + { + if (span.IsWhiteSpace ()) { + sb.Append (span).AppendLine(); + } else { + WriteTabs ().Append (span).AppendLine (); + } + + return this; + } + public TabbedStringBuilder AppendFormatLine (string format, params object [] args) { WriteTabs().AppendFormat (format, args); @@ -101,10 +133,13 @@ public TabbedStringBuilder AppendRaw (string rawString) { // we will split the raw string in lines and then append them so that the // tabbing is correct - var lines = rawString.Split (['\n'], StringSplitOptions.None); - for (var index = 0; index < lines.Length; index++) { - var line = lines [index]; - if (index == lines.Length - 1) { + var span = rawString.AsSpan (); + Span lines = stackalloc Range [span.Count ('\n')]; + var count = rawString.AsSpan().Split (lines, '\n'); + foreach (var range in lines) { + count--; + var line = rawString.AsSpan (range); + if (count <= 0) { Append (line); } else { AppendLine (line); From 2e3ff80afde1bd3816bb4da78bf5b3a36b55d572 Mon Sep 17 00:00:00 2001 From: Manuel de la Pena Date: Thu, 17 Oct 2024 14:43:59 -0400 Subject: [PATCH 13/22] Provide Appends that take an interpolated string. --- .../TabbedStringBuilder.cs | 16 ++++++++++----- .../TabbedStringBuilderTests.cs | 20 +++++++++++-------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs b/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs index 145ba478f498..ea5c42bcc787 100644 --- a/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs +++ b/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.CompilerServices; using System.Text; namespace Microsoft.Macios.Generator; @@ -10,7 +11,7 @@ namespace Microsoft.Macios.Generator; /// way we do not need to keep track of the current indentation level. /// /// var classBlock = new TabbedStringBuilder (sb); -/// classBlock.AppendFormatLine ("public static NSString? GetConstant (this {0} self)", enumSymbol.Name); +/// classBlock.AppendLine ("public static NSString? GetConstant (this {enumSymbol.Name} self)"); /// // open a new {} block, no need to keep track of the indentation, the new block has it /// using (var getConstantBlock = classBlock.CreateBlock (isBlock: true)) { /// // write the contents of the method here. @@ -117,13 +118,18 @@ public TabbedStringBuilder AppendLine (ReadOnlySpan span) return this; } - public TabbedStringBuilder AppendFormatLine (string format, params object [] args) + public TabbedStringBuilder Append (ref DefaultInterpolatedStringHandler handler) { - WriteTabs().AppendFormat (format, args); - sb.AppendLine (); + WriteTabs ().Append (handler.ToStringAndClear ()); return this; } - + + public TabbedStringBuilder AppendLine (ref DefaultInterpolatedStringHandler handler) + { + WriteTabs ().Append (handler.ToStringAndClear ()).AppendLine(); + return this; + } + /// /// Append a new raw literal by prepending the correct indentation. /// diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/TabbedStringBuilderTests.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/TabbedStringBuilderTests.cs index 5fc924f05523..7c7510ac6096 100644 --- a/tests/rgen/Microsoft.Macios.Generator.Tests/TabbedStringBuilderTests.cs +++ b/tests/rgen/Microsoft.Macios.Generator.Tests/TabbedStringBuilderTests.cs @@ -69,20 +69,24 @@ public void AppendLineStringTest (string line, uint tabCount, string expectedTab } [Theory] - [InlineData ("// this is a string {0}", "example", 0, "")] - [InlineData ("var t = {0};", 1, 1, "\t")] - [InlineData ("Console.WriteLine (\"{0}\")", "Hello", 5, "\t\t\t\t\t")] - public void AppendFormatLineTest (string format, object formatParam, uint tabCount, string expectedTabs) + [InlineData (0, "")] + [InlineData (1, "\t")] + [InlineData (5, "\t\t\t\t\t")] + public void AppendInterpolatedLineTest (uint tabCount, string expectedTabs) { - var line = string.Format (format, formatParam); - var expected = $"{expectedTabs}{{\n{expectedTabs}\t{line}\n{expectedTabs}}}\n"; string result; + var val1 = "Hello"; + var val2 = "World"; + var val3 = '!'; + var line = "Hello World!"; + var expected = $"{expectedTabs}{{\n{expectedTabs}\t{line}\n{expectedTabs}}}\n"; using (var block = new TabbedStringBuilder (sb, tabCount, true)) { - block.AppendFormatLine (format, formatParam); + block.AppendLine ($"{val1} {val2}{val3}"); result = block.ToString (); } Assert.Equal (expected, result); } + [Theory] [InlineData (0, "")] @@ -138,7 +142,7 @@ public void AppendGeneratedCodeAttributeOptimizableTest (uint tabCount, string e var expected = $"{expectedTabs}[BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]\n"; string result; using (var block = new TabbedStringBuilder (sb, tabCount)) { - block.AppendGeneratedCodeAttribute (true); + block.AppendGeneratedCodeAttribute (); result = block.ToString (); } Assert.Equal (expected, result); From cc24fa9621f18f071918567ca5dc80a79add9e00 Mon Sep 17 00:00:00 2001 From: GitHub Actions Autoformatter Date: Thu, 17 Oct 2024 18:48:59 +0000 Subject: [PATCH 14/22] Auto-format source code --- .../Microsoft.Macios.Generator/TabbedStringBuilder.cs | 10 +++++----- .../TabbedStringBuilderTests.cs | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs b/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs index ea5c42bcc787..5900a0dced1b 100644 --- a/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs +++ b/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs @@ -110,7 +110,7 @@ public TabbedStringBuilder AppendLine (string line) public TabbedStringBuilder AppendLine (ReadOnlySpan span) { if (span.IsWhiteSpace ()) { - sb.Append (span).AppendLine(); + sb.Append (span).AppendLine (); } else { WriteTabs ().Append (span).AppendLine (); } @@ -123,13 +123,13 @@ public TabbedStringBuilder Append (ref DefaultInterpolatedStringHandler handler) WriteTabs ().Append (handler.ToStringAndClear ()); return this; } - + public TabbedStringBuilder AppendLine (ref DefaultInterpolatedStringHandler handler) { - WriteTabs ().Append (handler.ToStringAndClear ()).AppendLine(); + WriteTabs ().Append (handler.ToStringAndClear ()).AppendLine (); return this; } - + /// /// Append a new raw literal by prepending the correct indentation. /// @@ -141,7 +141,7 @@ public TabbedStringBuilder AppendRaw (string rawString) // tabbing is correct var span = rawString.AsSpan (); Span lines = stackalloc Range [span.Count ('\n')]; - var count = rawString.AsSpan().Split (lines, '\n'); + var count = rawString.AsSpan ().Split (lines, '\n'); foreach (var range in lines) { count--; var line = rawString.AsSpan (range); diff --git a/tests/rgen/Microsoft.Macios.Generator.Tests/TabbedStringBuilderTests.cs b/tests/rgen/Microsoft.Macios.Generator.Tests/TabbedStringBuilderTests.cs index 7c7510ac6096..0333e5b138d0 100644 --- a/tests/rgen/Microsoft.Macios.Generator.Tests/TabbedStringBuilderTests.cs +++ b/tests/rgen/Microsoft.Macios.Generator.Tests/TabbedStringBuilderTests.cs @@ -86,7 +86,7 @@ public void AppendInterpolatedLineTest (uint tabCount, string expectedTabs) } Assert.Equal (expected, result); } - + [Theory] [InlineData (0, "")] From 4da5c5d28352da386b8824719ebc8a8b18b1c48b Mon Sep 17 00:00:00 2001 From: Manuel de la Pena Date: Thu, 17 Oct 2024 15:16:23 -0400 Subject: [PATCH 15/22] Use a better approach fro the new line in the append raw. --- .../TabbedStringBuilder.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs b/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs index 5900a0dced1b..d288c9884843 100644 --- a/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs +++ b/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; using System.Text; @@ -140,16 +141,14 @@ public TabbedStringBuilder AppendRaw (string rawString) // we will split the raw string in lines and then append them so that the // tabbing is correct var span = rawString.AsSpan (); - Span lines = stackalloc Range [span.Count ('\n')]; - var count = rawString.AsSpan ().Split (lines, '\n'); + var lines = rawString.AsSpan().Split ('\n'); + var count = 0; foreach (var range in lines) { - count--; + if (count > 0) + AppendLine (); var line = rawString.AsSpan (range); - if (count <= 0) { - Append (line); - } else { - AppendLine (line); - } + Append (line); + count++; } return this; } From 721b29e5e93e17599b674bb0df4946f6f3f1de2d Mon Sep 17 00:00:00 2001 From: GitHub Actions Autoformatter Date: Thu, 17 Oct 2024 19:19:34 +0000 Subject: [PATCH 16/22] Auto-format source code --- src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs b/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs index d288c9884843..e12f2d56cf95 100644 --- a/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs +++ b/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs @@ -141,7 +141,7 @@ public TabbedStringBuilder AppendRaw (string rawString) // we will split the raw string in lines and then append them so that the // tabbing is correct var span = rawString.AsSpan (); - var lines = rawString.AsSpan().Split ('\n'); + var lines = rawString.AsSpan ().Split ('\n'); var count = 0; foreach (var range in lines) { if (count > 0) From 1eaadfdc5b0ec330eed8d45744785d6ea38705b0 Mon Sep 17 00:00:00 2001 From: Manuel de la Pena Date: Thu, 17 Oct 2024 15:22:33 -0400 Subject: [PATCH 17/22] Update src/rgen/Microsoft.Macios.Generator.Sample/Microsoft.Macios.Generator.Sample.csproj --- .../Microsoft.Macios.Generator.Sample.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rgen/Microsoft.Macios.Generator.Sample/Microsoft.Macios.Generator.Sample.csproj b/src/rgen/Microsoft.Macios.Generator.Sample/Microsoft.Macios.Generator.Sample.csproj index 3c189f7b610e..e10b90a1b8fd 100644 --- a/src/rgen/Microsoft.Macios.Generator.Sample/Microsoft.Macios.Generator.Sample.csproj +++ b/src/rgen/Microsoft.Macios.Generator.Sample/Microsoft.Macios.Generator.Sample.csproj @@ -13,7 +13,7 @@ - external\BindginTypeAttribute.cs + external\BindingTypeAttribute.cs external\Attributes.cs From b6924fb5a450b7b0d5fb85edde5dff07610697ee Mon Sep 17 00:00:00 2001 From: Manuel de la Pena Date: Thu, 17 Oct 2024 15:24:48 -0400 Subject: [PATCH 18/22] Apply suggestions from code review --- .../BindingSourceGeneratorGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs b/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs index 562f337fc8e6..8b7d54e8533f 100644 --- a/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs +++ b/src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs @@ -63,7 +63,7 @@ static void AddPipeline (IncrementalGeneratorInitializationContext context) w /// /// Context used by the generator. /// The BaseTypeDeclarationSyntax we are interested in. - /// + /// A tuple that contains the BaseTypeDeclaration that was processed and a boolean that states if it should be processed or not. static (T Declaration, bool BindingAttributeFound) GetDeclarationForSourceGen (GeneratorSyntaxContext context) where T : BaseTypeDeclarationSyntax { From a208851451363b6d74b804f791fba013c37ca595 Mon Sep 17 00:00:00 2001 From: Manuel de la Pena Date: Fri, 18 Oct 2024 13:41:12 -0400 Subject: [PATCH 19/22] Apply suggestions from code review Co-authored-by: Rolf Bjarne Kvinge --- .../Microsoft.Macios.Generator/TabbedStringBuilder.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs b/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs index e12f2d56cf95..049637662b18 100644 --- a/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs +++ b/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs @@ -37,9 +37,10 @@ public TabbedStringBuilder (StringBuilder builder, uint currentCount = 0, bool b isBlock = block; if (isBlock) { // increase by 1 because we are in a block - tabCount = currentCount + 1; - var braceTab = new string ('\t', (int) (tabCount - 1)); - this.sb.AppendLine ($"{braceTab}{{"); + tabCount = currentCount; + WriteTabs (); + this.sb.Append ('{'); + tabCount++; } else { tabCount = currentCount; } @@ -223,6 +224,6 @@ public void Dispose () disposed = true; sb.Append ('\t', (int) tabCount - 1); - sb.AppendLine ("}"); + sb.Append ('}').AppendLine (); } } From c59f13c974cee2775f2fb8d4c9867aa3edae0d49 Mon Sep 17 00:00:00 2001 From: Manuel de la Pena Date: Fri, 18 Oct 2024 13:44:54 -0400 Subject: [PATCH 20/22] Fix tabbeb string build that broke after applying comments from PR. --- src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs b/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs index 049637662b18..ced71e6e51d5 100644 --- a/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs +++ b/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs @@ -38,8 +38,7 @@ public TabbedStringBuilder (StringBuilder builder, uint currentCount = 0, bool b if (isBlock) { // increase by 1 because we are in a block tabCount = currentCount; - WriteTabs (); - this.sb.Append ('{'); + WriteTabs ().Append ('{').AppendLine(); tabCount++; } else { tabCount = currentCount; From 16624321e39c4e13dbc487172951bae632b05be7 Mon Sep 17 00:00:00 2001 From: Manuel de la Pena Date: Fri, 18 Oct 2024 13:47:10 -0400 Subject: [PATCH 21/22] Remove method that will be re-added in the following rgen pr. --- .../Context/RootBindingContext.cs | 52 ------------------- 1 file changed, 52 deletions(-) diff --git a/src/rgen/Microsoft.Macios.Generator/Context/RootBindingContext.cs b/src/rgen/Microsoft.Macios.Generator/Context/RootBindingContext.cs index 08eb2a0390c4..64159cb0657b 100644 --- a/src/rgen/Microsoft.Macios.Generator/Context/RootBindingContext.cs +++ b/src/rgen/Microsoft.Macios.Generator/Context/RootBindingContext.cs @@ -44,56 +44,4 @@ public RootBindingContext (Compilation compilation) } } } - - // TODO: clean code coming from the old generator - public bool TryComputeLibraryName (string attributeLibraryName, string typeNamespace, - [NotNullWhen (true)] out string? libraryName, - out string? libraryPath) - { - libraryPath = null; - - if (!string.IsNullOrEmpty (attributeLibraryName)) { - // Remapped - libraryName = attributeLibraryName; - if (libraryName [0] == '+') { - switch (libraryName) { - case "+CoreImage": - CurrentPlatform.TryGetCoreImageMap (out libraryName); - break; - case "+CoreServices": - CurrentPlatform.TryGetCoreServicesMap (out libraryName); - break; - case "+PDFKit": - libraryName = "PdfKit"; - CurrentPlatform.TryGetPDFKitMap (out libraryPath); - break; - } - } else { - // we get something in LibraryName from FieldAttribute so we assume - // it is a path to a library, so we save the path and change library name - // to a valid identifier if needed - libraryPath = libraryName; - // without extension makes more sense, but we can't change it since it breaks compat - if (BindThirdPartyLibrary) { - libraryName = Path.GetFileName (libraryName); - } else { - libraryName = Path.GetFileNameWithoutExtension (libraryName); - } - - if (libraryName.Contains ('.')) - libraryName = libraryName.Replace (".", string.Empty); - } - } else if (BindThirdPartyLibrary) { - // User should provide a LibraryName - libraryName = null; - return false; - } else { - libraryName = typeNamespace; - } - - if (libraryName is not null && !_libraries.ContainsKey (libraryName)) - _libraries.Add (libraryName, libraryPath); - - return true; - } } From dbbdf510ede7dd9527cad254fd62a7c53e092cb2 Mon Sep 17 00:00:00 2001 From: GitHub Actions Autoformatter Date: Fri, 18 Oct 2024 17:48:15 +0000 Subject: [PATCH 22/22] Auto-format source code --- src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs b/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs index ced71e6e51d5..530279b8644c 100644 --- a/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs +++ b/src/rgen/Microsoft.Macios.Generator/TabbedStringBuilder.cs @@ -38,7 +38,7 @@ public TabbedStringBuilder (StringBuilder builder, uint currentCount = 0, bool b if (isBlock) { // increase by 1 because we are in a block tabCount = currentCount; - WriteTabs ().Append ('{').AppendLine(); + WriteTabs ().Append ('{').AppendLine (); tabCount++; } else { tabCount = currentCount;