Skip to content
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

Merged
merged 29 commits into from
Nov 19, 2024

Conversation

mandel-macaque
Copy link
Member

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.

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 \
Copy link
Member Author

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 :)

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

Copy link
Contributor

@stephen-hawley stephen-hawley left a 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.

public string SymbolName { get; set; }

/// <summary>
/// Get/Set the library that contains the symbol..
Copy link
Contributor

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>
Copy link
Contributor

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>]

Copy link
Member Author

@mandel-macaque mandel-macaque Nov 1, 2024

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;
Copy link
Contributor

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.

Copy link
Member Author

@mandel-macaque mandel-macaque Nov 1, 2024

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) {
Copy link
Contributor

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);

Copy link
Member Author

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
Copy link
Contributor

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;
}

Copy link
Contributor

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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean IsSymbolPresent?

Copy link
Member Author

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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBD?

Copy link
Member Author

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
}
Copy link
Contributor

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
Copy link
Contributor

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?

Copy link
Member Author

@mandel-macaque mandel-macaque Nov 1, 2024

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.

@vs-mobiletools-engineering-service2

This comment has been minimized.

Copy link
Member

@rolfbjarne rolfbjarne left a 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/ObjCBindings/FieldAttribute.cs Outdated Show resolved Hide resolved
src/ObjCBindings/FieldAttribute.cs Outdated Show resolved Hide resolved
// Run generators and retrieve all results.
var runResult = RunGenerators (compilation);

// bad formed bindings should not generate code
Copy link
Member

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?

Copy link
Member Author

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.

Comment on lines +40 to +53
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 {
Copy link
Member

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.

mandel-macaque and others added 2 commits November 1, 2024 11:26
Co-authored-by: Rolf Bjarne Kvinge <rolf@xamarin.com>
Copy link
Contributor

github-actions bot commented Nov 1, 2024

⚠️ Your code has been reformatted. ⚠️

If this is not desired, add the actions-disable-autoformat label, and revert the reformatting commit.

If files unrelated to your change were modified, try reverting the reformatting commit + merging with the target branch (and push those changes).

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

Copy link
Contributor

github-actions bot commented Nov 1, 2024

⚠️ Your code has been reformatted. ⚠️

If this is not desired, add the actions-disable-autoformat label, and revert the reformatting commit.

If files unrelated to your change were modified, try reverting the reformatting commit + merging with the target branch (and push those changes).

@vs-mobiletools-engineering-service2
Copy link
Collaborator

🔥 [CI Build] Build failed 🔥

Build failed for the job 'Build macOS tests'

Pipeline on Agent
Hash: 255afa1d114d6afa7dc3bb000202464c3d4f49c1 [PR build]

Copy link
Contributor

github-actions bot commented Nov 1, 2024

⚠️ Your code has been reformatted. ⚠️

If this is not desired, add the actions-disable-autoformat label, and revert the reformatting commit.

If files unrelated to your change were modified, try reverting the reformatting commit + merging with the target branch (and push those changes).

Copy link
Contributor

github-actions bot commented Nov 1, 2024

⚠️ Your code has been reformatted. ⚠️

If this is not desired, add the actions-disable-autoformat label, and revert the reformatting commit.

If files unrelated to your change were modified, try reverting the reformatting commit + merging with the target branch (and push those changes).

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

Copy link
Contributor

⚠️ Your code has been reformatted. ⚠️

If this is not desired, add the actions-disable-autoformat label, and revert the reformatting commit.

If files unrelated to your change were modified, try reverting the reformatting commit + merging with the target branch (and push those changes).

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

@vs-mobiletools-engineering-service2

This comment has been minimized.

Copy link
Member

@dalexsoto dalexsoto left a 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!

@dalexsoto dalexsoto requested a review from rolfbjarne November 14, 2024 23:23
/// <summary>
/// MacOS platform availability. If null, the default versions are used.
/// </summary>
public PlatformAvailability? MacOSX { get; }
Copy link
Member

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.

Comment on lines 30 to 37
SymbolAvailability (PlatformAvailability? iOsAvailability, PlatformAvailability? tvOsAvailability,
PlatformAvailability? macCatalystAvailability, PlatformAvailability? macOSXAvailability)
{
iOS = iOsAvailability;
TvOS = tvOsAvailability;
MacCatalyst = macCatalystAvailability;
MacOSX = macOSXAvailability;
}
Copy link
Member

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.

Copy link
Member Author

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.

Comment on lines 20 to 21
readonly HashSet<ApplePlatform> supportedPlatforms =
[ApplePlatform.iOS, ApplePlatform.TVOS, ApplePlatform.MacOSX, ApplePlatform.MacCatalyst];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be static.

Suggested change
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];

Comment on lines 91 to 101
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);
Copy link
Member

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",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Path.Combine

Copy link
Contributor

⚠️ Your code has been reformatted. ⚠️

If this is not desired, add the actions-disable-autoformat label, and revert the reformatting commit.

If files unrelated to your change were modified, try reverting the reformatting commit + merging with the target branch (and push those changes).

@vs-mobiletools-engineering-service2
Copy link
Collaborator

💻 [CI Build] Windows Integration Tests passed 💻

All Windows Integration Tests passed.

Pipeline on Agent
Hash: 29ae4cd40dc96cdfb457774d3aee2caa64b4be2b [PR build]

@vs-mobiletools-engineering-service2
Copy link
Collaborator

💻 [CI Build] Tests on macOS M1 - Mac Monterey (12) passed 💻

All tests on macOS M1 - Mac Monterey (12) passed.

Pipeline on Agent
Hash: 29ae4cd40dc96cdfb457774d3aee2caa64b4be2b [PR build]

@vs-mobiletools-engineering-service2
Copy link
Collaborator

❗ API diff for current PR / commit (Breaking changes)

.NET (:heavy_exclamation_mark: Breaking changes :heavy_exclamation_mark:)
  • iOS: vsdrops gist (:heavy_exclamation_mark: Breaking changes :heavy_exclamation_mark:)
  • tvOS: vsdrops gist (:heavy_exclamation_mark: Breaking changes :heavy_exclamation_mark:)
  • MacCatalyst: vsdrops gist (:heavy_exclamation_mark: Breaking changes :heavy_exclamation_mark:)
  • macOS: vsdrops gist (:heavy_exclamation_mark: Breaking changes :heavy_exclamation_mark:)

✅ API diff vs stable

.NET (No breaking changes)

ℹ️ Generator diff

Generator Diff: vsdrops (html) vsdrops (raw diff) gist (raw diff) - Please review changes)

Pipeline on Agent
Hash: 29ae4cd40dc96cdfb457774d3aee2caa64b4be2b [PR build]

@vs-mobiletools-engineering-service2
Copy link
Collaborator

💻 [CI Build] Tests on macOS X64 - Mac Sonoma (14) passed 💻

All tests on macOS X64 - Mac Sonoma (14) passed.

Pipeline on Agent
Hash: 29ae4cd40dc96cdfb457774d3aee2caa64b4be2b [PR build]

@vs-mobiletools-engineering-service2
Copy link
Collaborator

💻 [CI Build] Tests on macOS M1 - Mac Ventura (13) passed 💻

All tests on macOS M1 - Mac Ventura (13) passed.

Pipeline on Agent
Hash: 29ae4cd40dc96cdfb457774d3aee2caa64b4be2b [PR build]

@vs-mobiletools-engineering-service2
Copy link
Collaborator

🚀 [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
✅ dotnettests (iOS): All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (MacCatalyst): All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (macOS): All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (Multiple platforms): All 1 tests passed. Html Report (VSDrops) Download
✅ dotnettests (tvOS): All 1 tests passed. Html Report (VSDrops) Download
✅ framework: All 2 tests passed. Html Report (VSDrops) Download
✅ fsharp: All 4 tests passed. Html Report (VSDrops) Download
✅ generator: All 3 tests passed. Html Report (VSDrops) Download
✅ interdependent-binding-projects: All 4 tests passed. Html Report (VSDrops) Download
✅ introspection: All 4 tests passed. Html Report (VSDrops) Download
✅ linker: All 40 tests passed. Html Report (VSDrops) Download
✅ monotouch (iOS): All 7 tests passed. Html Report (VSDrops) Download
✅ monotouch (MacCatalyst): All 8 tests passed. Html Report (VSDrops) Download
✅ monotouch (macOS): All 9 tests passed. Html Report (VSDrops) Download
✅ monotouch (tvOS): All 7 tests passed. Html Report (VSDrops) Download
✅ msbuild: All 2 tests passed. Html Report (VSDrops) Download
✅ xcframework: All 4 tests passed. Html Report (VSDrops) Download
✅ xtro: All 1 tests passed. Html Report (VSDrops) Download

Pipeline on Agent
Hash: 29ae4cd40dc96cdfb457774d3aee2caa64b4be2b [PR build]

@mandel-macaque mandel-macaque merged commit 6bfecec into main Nov 19, 2024
12 checks passed
@mandel-macaque mandel-macaque deleted the dev/mandel/rgen-smart-enums-generation branch November 19, 2024 11:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants