Skip to content

Commit dd5577b

Browse files
mandel-macaqueGitHub Actions AutoformatterCopilot
authored
[Rgen] Generate backward compatible strong dictionary keys. (#23405)
To keep API compatibility we add a custom code emitter that will generate the same code as bgen for the keys. This can be used with all the classes that were made internal or to not change the current manual code. --------- Co-authored-by: GitHub Actions Autoformatter <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent 97608c1 commit dd5577b

File tree

6 files changed

+257
-0
lines changed

6 files changed

+257
-0
lines changed

src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using Microsoft.CodeAnalysis;
1010
using Microsoft.CodeAnalysis.CSharp.Syntax;
1111
using Microsoft.CodeAnalysis.Text;
12+
using Microsoft.Macios.Generator.Attributes;
1213
using Microsoft.Macios.Generator.Context;
1314
using Microsoft.Macios.Generator.DataModel;
1415
using Microsoft.Macios.Generator.Emitters;
@@ -73,6 +74,23 @@ public void Initialize (IncrementalGeneratorInitializationContext context)
7374

7475
context.RegisterSourceOutput (context.CompilationProvider.Combine (asyncResultsProvider.Collect ()),
7576
((ctx, t) => GenerateAsyncResultCode (ctx, t.Right)));
77+
78+
if (GeneratorConfiguration.BGenCompatible) {
79+
// the following code generations will only be used when the generator is compatible with bgen.
80+
81+
var strongDictionaryKeysProvider = provider
82+
.Where (t => {
83+
if (t.Bindings.BindingType != BindingType.StrongDictionaryKeys)
84+
return false;
85+
// only want those kyes that we need to make backwards compatible with bgen
86+
var bindingInfo = (BindingTypeData<ObjCBindings.StrongDictionaryKeys>) t.Bindings.BindingInfo;
87+
return bindingInfo.Flags.HasFlag (ObjCBindings.StrongDictionaryKeys.BackwardCompatible);
88+
})
89+
.Select ((tuple, _) => (tuple.RootBindingContext, tuple.Bindings));
90+
91+
context.RegisterSourceOutput (context.CompilationProvider.Combine (strongDictionaryKeysProvider.Collect ()),
92+
((ctx, t) => GenerateBGenStrongDictionaryKeys (ctx, t.Right)));
93+
}
7694
}
7795

7896
/// <summary>
@@ -262,6 +280,43 @@ static void GenerateAsyncResultCode (SourceProductionContext context,
262280
}
263281
}
264282

283+
/// <summary>
284+
/// Code generator that emits strong dictionary keys classes compatible with bgen output.
285+
/// This generates backward-compatible strong dictionary keys when the BackwardCompatible flag is set.
286+
/// </summary>
287+
/// <param name="context">Source production context.</param>
288+
/// <param name="bindingsList">The bindings that need backward-compatible strong dictionary keys generation.</param>
289+
static void GenerateBGenStrongDictionaryKeys (SourceProductionContext context,
290+
in ImmutableArray<(RootContext Context, Binding Binding)> bindingsList)
291+
{
292+
if (bindingsList.Length == 0)
293+
return;
294+
// all items contain the same root context, we can get it from the first item
295+
var rootBindingContext = bindingsList [0].Context;
296+
297+
// loop over all the bindings that have to be backwards compatible with bgen
298+
// and generate the code for them.
299+
var sb = new TabbedStringBuilder (new ());
300+
var emitter = new BGenStrongDictionaryKeysEmitter ();
301+
foreach (var (_, binding) in bindingsList) {
302+
// init sb and add the header
303+
sb.Clear ();
304+
sb.WriteHeader ();
305+
var bindingContext = new BindingContext (rootBindingContext, sb, binding);
306+
if (emitter.TryEmit (bindingContext, out var diagnostics)) {
307+
// only add a file when we do generate code
308+
var code = sb.ToCode ();
309+
var namespacePath = Path.Combine (binding.Namespace.ToArray ());
310+
var fileName = emitter.GetSymbolName (binding);
311+
context.AddSource ($"{Path.Combine (namespacePath, fileName)}.g.cs",
312+
SourceText.From (code, Encoding.UTF8));
313+
} else {
314+
// add to the diagnostics and continue to the next possible candidate
315+
context.ReportDiagnostics (diagnostics);
316+
}
317+
}
318+
}
319+
265320
/// <summary>
266321
/// Collect the using statements from the named ype code changes and add them to the string builder
267322
/// that will be used to generate the code. This way we ensure that we have all the namespaces needed by the

src/rgen/Microsoft.Macios.Generator/Configuration.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,9 @@ public static class GeneratorConfiguration {
99
/// Use the global namespace for the generated code.
1010
/// </summary>
1111
public const bool UseGlobalNamespace = true;
12+
13+
/// <summary>
14+
/// Generate code compatible with bgen output for backward compatibility.
15+
/// </summary>
16+
public const bool BGenCompatible = true;
1217
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System.Collections.Generic;
5+
using System.Collections.Immutable;
6+
using System.Diagnostics.CodeAnalysis;
7+
using System.Linq;
8+
using Microsoft.CodeAnalysis;
9+
using Microsoft.Macios.Generator.Attributes;
10+
using Microsoft.Macios.Generator.Context;
11+
using Microsoft.Macios.Generator.DataModel;
12+
using Microsoft.Macios.Generator.IO;
13+
14+
namespace Microsoft.Macios.Generator.Emitters;
15+
16+
/// <summary>
17+
/// Emitter responsible for generating strong dictionary keys classes compatible with bgen output.
18+
/// This emitter mimics the behavior of bgen for strong dictionary keys generation.
19+
/// </summary>
20+
class BGenStrongDictionaryKeysEmitter : IClassEmitter {
21+
22+
/// <inheritdoc />
23+
public string GetSymbolName (in Binding binding)
24+
{
25+
// in this case the symbol name is the name passed to the binding type attr
26+
var bindingTypeData = (BindingTypeData<ObjCBindings.StrongDictionaryKeys>) binding.BindingInfo;
27+
return bindingTypeData.Name ?? string.Empty;
28+
}
29+
30+
/// <inheritdoc />
31+
public IEnumerable<string> UsingStatements { get; } = [];
32+
33+
/// <inheritdoc />
34+
public bool TryEmit (in BindingContext bindingContext, [NotNullWhen (false)] out ImmutableArray<Diagnostic>? diagnostics)
35+
{
36+
diagnostics = null;
37+
if (bindingContext.Changes.BindingType != BindingType.StrongDictionaryKeys) {
38+
diagnostics = [Diagnostic.Create (
39+
Diagnostics
40+
.RBI0000, // An unexpected error occurred while processing '{0}'. Please fill a bug report at https://github.com/dotnet/macios/issues/new.
41+
null,
42+
bindingContext.Changes.FullyQualifiedSymbol)];
43+
return false;
44+
}
45+
// emit a preprocessor directive so that we do not include the file if we are in XAMCORE_5
46+
bindingContext.Builder.WriteLine ("#if !XAMCORE_5_0");
47+
48+
// this code emitter is trying to copy what bgen generates, we will use the namespace of the type, but because
49+
// bgen does not have the keys as a nester class, we are going to ignore the outer classes.
50+
this.EmitNamespace (bindingContext);
51+
52+
// add an obsolete attribute to the class so that it is clear that this is a bgen generated class and the new nested
53+
// strong dictionary keys should be used instead.
54+
var outerClasses = string.Join ('.', bindingContext.Changes.OuterClasses.Select (x => x.Name));
55+
bindingContext.Builder.WriteLine ($"[Obsolete (\"Use '{outerClasses}.{bindingContext.Changes.Name}' class instead.\", false)]");
56+
bindingContext.Builder.AppendMemberAvailability (bindingContext.Changes.SymbolAvailability);
57+
var modifiers = $"{string.Join (' ', bindingContext.Changes.Modifiers)} ";
58+
var bindingTypeData = (BindingTypeData<ObjCBindings.StrongDictionaryKeys>) bindingContext.Changes.BindingInfo;
59+
using (var classBlock = bindingContext.Builder.CreateBlock (
60+
$"{(string.IsNullOrWhiteSpace (modifiers) ? string.Empty : modifiers)}class {bindingTypeData.Name}",
61+
true)) {
62+
// the only thing we care about is the keys, so we emit the keys
63+
this.EmitFields (bindingContext.Changes.Name, bindingContext.Changes.Properties, classBlock,
64+
out var _);
65+
}
66+
67+
bindingContext.Builder.WriteLine ("#endif");
68+
return true;
69+
}
70+
71+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// <auto-generated />
2+
3+
#nullable enable
4+
5+
#if !XAMCORE_5_0
6+
7+
namespace TestNamespace;
8+
9+
[Obsolete ("Use 'CARendererOptions.Keys' class instead.", false)]
10+
[SupportedOSPlatform ("macos")]
11+
[SupportedOSPlatform ("ios")]
12+
[SupportedOSPlatform ("tvos")]
13+
[SupportedOSPlatform ("maccatalyst13.1")]
14+
public static partial class CARendererOptionKeys
15+
{
16+
17+
static global::Foundation.NSString? _ColorSpace;
18+
19+
[SupportedOSPlatform ("macos")]
20+
[SupportedOSPlatform ("ios")]
21+
[SupportedOSPlatform ("tvos")]
22+
[SupportedOSPlatform ("maccatalyst13.1")]
23+
[BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
24+
public static partial global::Foundation.NSString ColorSpace
25+
{
26+
[SupportedOSPlatform ("macos")]
27+
[SupportedOSPlatform ("ios")]
28+
[SupportedOSPlatform ("tvos")]
29+
[SupportedOSPlatform ("maccatalyst13.1")]
30+
get
31+
{
32+
if (_ColorSpace is null)
33+
_ColorSpace = global::ObjCRuntime.Dlfcn.GetStringConstant (global::ObjCRuntime.Libraries.TestNamespace.Handle, "AVCaptureDeviceTypeBuiltInTelephotoCamera")!;
34+
return _ColorSpace;
35+
}
36+
}
37+
38+
static global::Foundation.NSString? _MetalCommandQueue;
39+
40+
[SupportedOSPlatform ("macos")]
41+
[SupportedOSPlatform ("ios")]
42+
[SupportedOSPlatform ("tvos")]
43+
[SupportedOSPlatform ("maccatalyst13.1")]
44+
[BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
45+
public static partial global::Foundation.NSString MetalCommandQueue
46+
{
47+
[SupportedOSPlatform ("macos")]
48+
[SupportedOSPlatform ("ios")]
49+
[SupportedOSPlatform ("tvos")]
50+
[SupportedOSPlatform ("maccatalyst13.1")]
51+
get
52+
{
53+
if (_MetalCommandQueue is null)
54+
_MetalCommandQueue = global::ObjCRuntime.Dlfcn.GetStringConstant (global::ObjCRuntime.Libraries.TestNamespace.Handle, "kCARendererMetalCommandQueue")!;
55+
return _MetalCommandQueue;
56+
}
57+
}
58+
}
59+
#endif
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// <auto-generated />
2+
3+
#nullable enable
4+
5+
#if !XAMCORE_5_0
6+
7+
namespace TestNamespace;
8+
9+
[Obsolete ("Use 'OuterClass.InnerClass.NestedStrongDictionary.Keys' class instead.", false)]
10+
[SupportedOSPlatform ("macos")]
11+
[SupportedOSPlatform ("ios")]
12+
[SupportedOSPlatform ("tvos")]
13+
[SupportedOSPlatform ("maccatalyst13.1")]
14+
public static partial class CARendererOptionKeys
15+
{
16+
17+
static global::Foundation.NSString? _ColorSpace;
18+
19+
[SupportedOSPlatform ("macos")]
20+
[SupportedOSPlatform ("ios")]
21+
[SupportedOSPlatform ("tvos")]
22+
[SupportedOSPlatform ("maccatalyst13.1")]
23+
[BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
24+
public static partial global::Foundation.NSString ColorSpace
25+
{
26+
[SupportedOSPlatform ("macos")]
27+
[SupportedOSPlatform ("ios")]
28+
[SupportedOSPlatform ("tvos")]
29+
[SupportedOSPlatform ("maccatalyst13.1")]
30+
get
31+
{
32+
if (_ColorSpace is null)
33+
_ColorSpace = global::ObjCRuntime.Dlfcn.GetStringConstant (global::ObjCRuntime.Libraries.TestNamespace.Handle, "AVCaptureDeviceTypeBuiltInTelephotoCamera")!;
34+
return _ColorSpace;
35+
}
36+
}
37+
38+
static global::Foundation.NSString? _MetalCommandQueue;
39+
40+
[SupportedOSPlatform ("macos")]
41+
[SupportedOSPlatform ("ios")]
42+
[SupportedOSPlatform ("tvos")]
43+
[SupportedOSPlatform ("maccatalyst13.1")]
44+
[BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
45+
public static partial global::Foundation.NSString MetalCommandQueue
46+
{
47+
[SupportedOSPlatform ("macos")]
48+
[SupportedOSPlatform ("ios")]
49+
[SupportedOSPlatform ("tvos")]
50+
[SupportedOSPlatform ("maccatalyst13.1")]
51+
get
52+
{
53+
if (_MetalCommandQueue is null)
54+
_MetalCommandQueue = global::ObjCRuntime.Dlfcn.GetStringConstant (global::ObjCRuntime.Libraries.TestNamespace.Handle, "kCARendererMetalCommandQueue")!;
55+
return _MetalCommandQueue;
56+
}
57+
}
58+
}
59+
#endif

tests/rgen/Microsoft.Macios.Generator.Tests/StrongDictionaries/StrongDictionaryTests.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,42 +26,50 @@ public class TestDataGenerator : BaseTestDataGenerator, IEnumerable<object []> {
2626
new (ApplePlatform.iOS, "CARendererOptions", "CARendererOptions.cs", "ExpectedCARendererOptions.cs") {
2727
ExtraFiles = new () {
2828
{"CARendererOptions.Keys.g.cs", "ExpectedCARendererOptionsKeys.cs"},
29+
{"CARendererOptionKeys.g.cs", "ExpectedBGenKeys.cs"},
2930
}
3031
},
3132
new (ApplePlatform.TVOS, "CARendererOptions", "CARendererOptions.cs", "ExpectedCARendererOptions.cs") {
3233
ExtraFiles = new () {
3334
{"CARendererOptions.Keys.g.cs", "ExpectedCARendererOptionsKeys.cs"},
35+
{"CARendererOptionKeys.g.cs", "ExpectedBGenKeys.cs"},
3436
}
3537
},
3638
new (ApplePlatform.MacCatalyst, "CARendererOptions", "CARendererOptions.cs", "ExpectedCARendererOptions.cs") {
3739
ExtraFiles = new () {
3840
{"CARendererOptions.Keys.g.cs", "ExpectedCARendererOptionsKeys.cs"},
41+
{"CARendererOptionKeys.g.cs", "ExpectedBGenKeys.cs"},
3942
}
4043
},
4144
new (ApplePlatform.MacOSX, "CARendererOptions", "CARendererOptions.cs", "ExpectedCARendererOptions.cs") {
4245
ExtraFiles = new () {
4346
{"CARendererOptions.Keys.g.cs", "ExpectedCARendererOptionsKeys.cs"},
47+
{"CARendererOptionKeys.g.cs", "ExpectedBGenKeys.cs"},
4448
}
4549
},
4650

4751
new (ApplePlatform.iOS, "NestedStrongDictionary", "NestedStrongDictionary.cs", "ExpectedNestedStrongDictionary.cs") {
4852
ExtraFiles = new () {
4953
{"NestedStrongDictionary.Keys.g.cs", "ExpectedNestedNestedStrongDictionaryKeys.cs"},
54+
{"CARendererOptionKeys.g.cs", "ExpectedNestedBGenKeys.cs"},
5055
}
5156
},
5257
new (ApplePlatform.TVOS, "NestedStrongDictionary", "NestedStrongDictionary.cs", "ExpectedNestedStrongDictionary.cs") {
5358
ExtraFiles = new () {
5459
{"NestedStrongDictionary.Keys.g.cs", "ExpectedNestedNestedStrongDictionaryKeys.cs"},
60+
{"CARendererOptionKeys.g.cs", "ExpectedNestedBGenKeys.cs"},
5561
}
5662
},
5763
new (ApplePlatform.MacCatalyst, "NestedStrongDictionary", "NestedStrongDictionary.cs", "ExpectedNestedStrongDictionary.cs") {
5864
ExtraFiles = new () {
5965
{"NestedStrongDictionary.Keys.g.cs", "ExpectedNestedNestedStrongDictionaryKeys.cs"},
66+
{"CARendererOptionKeys.g.cs", "ExpectedNestedBGenKeys.cs"},
6067
}
6168
},
6269
new (ApplePlatform.MacOSX, "NestedStrongDictionary", "NestedStrongDictionary.cs", "ExpectedNestedStrongDictionary.cs") {
6370
ExtraFiles = new () {
6471
{"NestedStrongDictionary.Keys.g.cs", "ExpectedNestedNestedStrongDictionaryKeys.cs"},
72+
{"CARendererOptionKeys.g.cs", "ExpectedNestedBGenKeys.cs"},
6573
}
6674
},
6775
};

0 commit comments

Comments
 (0)