-
Notifications
You must be signed in to change notification settings - Fork 515
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Rgen] Implement the generation of the smart enums. #21562
Conversation
Add the code generation for smart enumerators. This means the following changes: 1. Refactor the way we register the diff pipelines. 2. Add code that generates the Extension class for the enums. 3. Add code that will generate the Library.g.cs partial class.
@@ -460,6 +460,7 @@ $($(2)_DOTNET_BUILD_DIR)/$(4)/Microsoft.$(1)%dll $($(2)_DOTNET_BUILD_DIR)/$(4)/M | |||
$(DOTNET_FLAGS) \ | |||
/analyzer:$(ROSLYN_GENERATOR_COMMON) \ | |||
/analyzer:$(ROSLYN_GENERATOR) \ | |||
/generatedfilesout:$($(2)_DOTNET_BUILD_DIR)/generated-sources \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Allows to see the generator API diff :)
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Love all the tests.
src/ObjCBindings/FieldAttribute.cs
Outdated
public string SymbolName { get; set; } | ||
|
||
/// <summary> | ||
/// Get/Set the library that contains the symbol.. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like this to be clear as to whether it is:
- the name of the library
- a path to the library and whether or not it is absolute and/or relative and to what it is relative (RPATH?)
/// Create a new FieldAttribute for the given symbol in the provided library and customizing the flags. | ||
/// <param name="symbolName">The name of the symbol.</param> | ||
/// <param name="libraryName">The name of the library that contains the symbol.</param> | ||
/// <param name="flags">The flags to customize the field.</param> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it a requirement that the enum should have FlagsAttribute
on it? If so can we put a check in the constructor to catch incorrect usage such as:
[Field<StringComparison>]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it does not need to have a flag. The default flat will be use in that case. 'default (T)' in C# on an enumerator will always return the enumerator value that is mapped to 0. We can add a code analyzer for exactly that, coming in the next PR.
public FieldAttribute (string symbolName, string libraryName, T? flags) | ||
{ | ||
SymbolName = symbolName; | ||
LibraryName = libraryName; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LibraryName
is a nullable string. I think you'd be better off with this constructor:
public FieldAttribute (string symbolName, string? libraryName = null, T? flags = default (T))
{
SymbolName = symbolName;
LibraryName = libraryName;
Flags = flags
}
Then you can eliminate the other two constructors entirely.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The issue with that is that it does not allow to do the following:
[Field<Method>("Foo", Method.Appearance)]
public int Test { get; }
The compiler will complain with:
Error CS1503 : Argument 2: cannot convert from 'TestCSharp.AttrsTest.Method' to 'string?'
So for the user to only provide the flags they will have to use one of the following:
[Field<Method>("Foo", flags:Method.Appearance)]
public int Test { get; }
[Field<Method>("Foo", null, Method.Appearance)]
public int Test { get; }
I wanted to provide an API in which we have to write the least amount.
SourceText.From (code, Encoding.UTF8)); | ||
} else { | ||
// add to the diagnostics and continue to the next possible candidate | ||
foreach (Diagnostic diagnostic in diagnostics) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: foreach (var diagnostic...)
or if you don't care about setting a breakpoint:
diagnostics.ForEach (context.ReportDiagnostic);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved that to an extension method as per rolf comments.
} | ||
|
||
/// <summary> | ||
/// Code generator that emmits the static classes that contain the pointers to the library used |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
emmits -> emits
emitter = null; | ||
break; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this pattern.
public string SymbolName => "Libraries"; | ||
INamedTypeSymbol? LibrarySymbol { get; } = context.Compilation.GetTypeByMetadataName ("ObjCRuntime.Libraries"); | ||
|
||
bool IsSymbolPreset (string className) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you mean IsSymbolPresent
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I'll fix that.
public IEnumerable<string> UsingStatements { get; } = []; | ||
public bool TryEmit ([NotNullWhen (false)] out ImmutableArray<Diagnostic>? diagnostics) | ||
{ | ||
diagnostics = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TBD?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, work in progress
fieldBucket.Add ((Symbol: fieldSymbol, FieldData: fieldData)); | ||
} else { | ||
// TODO: diagnostics | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue?
if (string.IsNullOrEmpty (attrName)) | ||
continue; | ||
if (!attributes.TryAdd (attrName, attributeData)) { | ||
// TODO: diagnostics |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume that the name and signature of this method will change to a TryGet
pattern?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need to fix this. attrs that we are interested do not allow to be referenced multiple types on a symbol. If that happens we introduced and unexpected bug.
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is probably for a different PR, but I think it would be a good idea to add unit tests for memory usage as well (using something like BenchmarkDotNet), because it's really easy to end up using a lot of string memory unnecessarily (as can be seen in bgen, which uses multiple GBs of memory).
src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs
Outdated
Show resolved
Hide resolved
src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs
Outdated
Show resolved
Hide resolved
tests/rgen/Microsoft.Macios.Generator.Tests/SmartEnum/Data/ExpectedAVCaptureDeviceTypeEnum.cs
Outdated
Show resolved
Hide resolved
// Run generators and retrieve all results. | ||
var runResult = RunGenerators (compilation); | ||
|
||
// bad formed bindings should not generate code |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe assert that an error is reported too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Errors will come from the Analyzer which will have tests for it. Coming in the next PR.
src/rgen/Microsoft.Macios.Generator/BindingSourceGeneratorGenerator.cs
Outdated
Show resolved
Hide resolved
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 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see any tests for this logic.
src/rgen/Microsoft.Macios.Generator/Context/RootBindingContext.cs
Outdated
Show resolved
Hide resolved
Co-authored-by: Rolf Bjarne Kvinge <rolf@xamarin.com>
|
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
🔥 [CI Build] Build failed 🔥Build failed for the job 'Build macOS tests' Pipeline on Agent |
|
…amarin/xamarin-macios into dev/mandel/rgen-smart-enums-generation
|
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good, love the extensive testing!
/// <summary> | ||
/// MacOS platform availability. If null, the default versions are used. | ||
/// </summary> | ||
public PlatformAvailability? MacOSX { get; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: the correct platform name is macOS
(or MacOS
) - it's not really MacOSX anymore, it would be MacOSXV this year.
SymbolAvailability (PlatformAvailability? iOsAvailability, PlatformAvailability? tvOsAvailability, | ||
PlatformAvailability? macCatalystAvailability, PlatformAvailability? macOSXAvailability) | ||
{ | ||
iOS = iOsAvailability; | ||
TvOS = tvOsAvailability; | ||
MacCatalyst = macCatalystAvailability; | ||
MacOSX = macOSXAvailability; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this class could be implemented in a way that doesn't hardcode the platforms, then that would be great!
Over the years I've added 3 platforms, and removed 1 (although that's not quite completed yet), so I've firmly believe the fewer places that need updating whenever the platforms we support change, the better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
100% agree, it is annoying to implement it in a way without hardcoding due to the fact that we are trying to use a read only structure. I'll try to implement a smarter way in a smaller PR.
readonly HashSet<ApplePlatform> supportedPlatforms = | ||
[ApplePlatform.iOS, ApplePlatform.TVOS, ApplePlatform.MacOSX, ApplePlatform.MacCatalyst]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be static.
readonly HashSet<ApplePlatform> supportedPlatforms = | |
[ApplePlatform.iOS, ApplePlatform.TVOS, ApplePlatform.MacOSX, ApplePlatform.MacCatalyst]; | |
static readonly HashSet<ApplePlatform> supportedPlatforms = | |
[ApplePlatform.iOS, ApplePlatform.TVOS, ApplePlatform.MacOSX, ApplePlatform.MacCatalyst]; |
PlatformAvailability? iOS = | ||
platforms.ContainsKey (ApplePlatform.iOS) ? platforms [ApplePlatform.iOS].ToImmutable () : null; | ||
PlatformAvailability? tvOS = | ||
platforms.ContainsKey (ApplePlatform.TVOS) ? platforms [ApplePlatform.TVOS].ToImmutable () : null; | ||
PlatformAvailability? macCatalyst = | ||
platforms.ContainsKey (ApplePlatform.MacCatalyst) | ||
? platforms [ApplePlatform.MacCatalyst].ToImmutable () | ||
: null; | ||
PlatformAvailability? macOS = | ||
platforms.ContainsKey (ApplePlatform.MacOSX) ? platforms [ApplePlatform.MacOSX].ToImmutable () : null; | ||
return new (iOS, tvOS, macCatalyst, macOS); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this be implemented as a loop over supportedPlatforms
to avoid hardcoding the platforms?
if (emitter.TryEmit (out var diagnostics)) { | ||
// only add file when we do generate code | ||
var code = sb.ToString (); | ||
context.AddSource ($"{emitter.SymbolNamespace}/{emitter.SymbolName}.g.cs", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
context.AddSource ($"{emitter.SymbolNamespace}/{emitter.SymbolName}.g.cs", | |
context.AddSource ($"{Path.Combine (emitter.SymbolNamespace, emitter.SymbolName)}.g.cs", |
if (emitter.TryEmit (out var diagnostics)) { | ||
// only add file when we do generate code | ||
var code = sb.ToString (); | ||
context.AddSource ($"{emitter.SymbolNamespace}/{emitter.SymbolName}.g.cs", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Path.Combine
|
💻 [CI Build] Windows Integration Tests passed 💻✅ All Windows Integration Tests passed. Pipeline on Agent |
💻 [CI Build] Tests on macOS M1 - Mac Monterey (12) passed 💻✅ All tests on macOS M1 - Mac Monterey (12) passed. Pipeline on Agent |
❗ API diff for current PR / commit (Breaking changes).NET (:heavy_exclamation_mark: Breaking changes :heavy_exclamation_mark:)
✅ API diff vs stable.NET (No breaking changes)ℹ️ Generator diffGenerator Diff: vsdrops (html) vsdrops (raw diff) gist (raw diff) - Please review changes) Pipeline on Agent |
💻 [CI Build] Tests on macOS X64 - Mac Sonoma (14) passed 💻✅ All tests on macOS X64 - Mac Sonoma (14) passed. Pipeline on Agent |
💻 [CI Build] Tests on macOS M1 - Mac Ventura (13) passed 💻✅ All tests on macOS M1 - Mac Ventura (13) passed. Pipeline on Agent |
🚀 [CI Build] Test results 🚀Test results✅ All tests passed on VSTS: test results. 🎉 All 101 tests passed 🎉 Tests counts✅ cecil: All 1 tests passed. Html Report (VSDrops) Download Pipeline on Agent |
Add the code generation for smart enumerators. This means the following changes: