Skip to content

Commit

Permalink
fix(hr): Fix RegisterAttribute generation for nested/generic types
Browse files Browse the repository at this point in the history
  • Loading branch information
Youssef1313 committed Nov 7, 2023
1 parent 3c666a4 commit 2a17b6c
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Uno;
using Uno.Roslyn;
using Microsoft.CodeAnalysis.PooledObjects;
using System.Diagnostics;

namespace Microsoft.CodeAnalysis
{
Expand Down Expand Up @@ -398,7 +399,10 @@ ITypeSymbol ts when ts.ContainingType is ISymbol pt
return type?.GetFullyQualifiedTypeExcludingGlobal();
}

public static string GetFullMetadataName(this ITypeSymbol symbol)
// forRegisterAttributeDotReplacement is used specifically by NativeCtorsGenerator to generate for the Android/iOS RegisterAttribute
// A non-null value means we are generating for RegisterAttribute, and we replace invalid characters with '_'.
// The '.' is special cased to be replaced by the value of forRegisterAttributeDotReplacement, whether it's '_' or '/'
public static string GetFullMetadataName(this ITypeSymbol symbol, char? forRegisterAttributeDotReplacement = null)
{
ISymbol s = symbol;
var sb = new StringBuilder(s.MetadataName);
Expand Down Expand Up @@ -428,9 +432,19 @@ public static string GetFullMetadataName(this ITypeSymbol symbol)

var namedType = symbol as INamedTypeSymbol;

if (namedType?.TypeArguments.Any() ?? false)
// When generating for RegisterAttribute, the name we pass to the attribute is used by Xamarin tooling for generating Java files, specifically, it's used for the class name.
// The characters '.', '+', and '`' are not valid characters for a class name.
// On Android, we use '/' as replacement for '.' to match Jni name:
// https://github.com/xamarin/java.interop/blob/38c8a827e78ffe9c80ad2313a9e0e0d4f8215184/src/Java.Interop.Tools.TypeNameMappings/Java.Interop.Tools.TypeNameMappings/JavaNativeTypeManager.cs#L693-L699
if (forRegisterAttributeDotReplacement.HasValue)
{
var genericArgs = namedType.TypeArguments.Select(GetFullMetadataName).JoinBy(",");
var replacement = forRegisterAttributeDotReplacement.Value;
sb.Replace('.', replacement).Replace('+', '_').Replace('`', '_');
}
else if (namedType?.TypeArguments.Any() ?? false)
{
// We don't append type arguments when generating for RegisterAttribute because '[' and ']' are invalid characters for a class name.
var genericArgs = namedType.TypeArguments.Select(a => GetFullMetadataName(a, null)).JoinBy(",");
sb.Append($"[{genericArgs}]");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Uno.UI.SourceGenerators.Helpers;
using System.Diagnostics;
using Uno.Extensions;
using Uno.Roslyn;

Expand Down Expand Up @@ -37,7 +36,7 @@ private class SerializationMethodsGenerator : SymbolVisitor
private readonly INamedTypeSymbol? _androidViewSymbol;
private readonly INamedTypeSymbol? _intPtrSymbol;
private readonly INamedTypeSymbol? _jniHandleOwnershipSymbol;
private readonly INamedTypeSymbol?[]? _javaCtorParams;
private readonly INamedTypeSymbol?[] _javaCtorParams;
private readonly string _configuration;
private readonly bool _isDebug;
private readonly bool _isHotReloadEnabled;
Expand Down Expand Up @@ -129,7 +128,7 @@ private void ProcessType(INamedTypeSymbol typeSymbol)

if (isAndroidView)
{
Func<IMethodSymbol, bool> predicate = m => m.Parameters.Select(p => p.Type).SequenceEqual(_javaCtorParams ?? Array.Empty<ITypeSymbol?>());
Func<IMethodSymbol, bool> predicate = m => m.Parameters.Select(p => p.Type).SequenceEqual(_javaCtorParams);
var nativeCtor = GetNativeCtor(typeSymbol, predicate, considerAllBaseTypes: false);

if (nativeCtor == null && GetNativeCtor(typeSymbol.BaseType, predicate, considerAllBaseTypes: true) != null)
Expand Down Expand Up @@ -164,7 +163,7 @@ string GetGeneratedCode(INamedTypeSymbol typeSymbol)
// generated at runtime
var registerParamApple = _isHotReloadEnabled
? $"\"{typeSymbol.GetFullMetadataName().Replace(".", "_")}\""
? $"\"{typeSymbol.GetFullMetadataName(forRegisterAttributeDotReplacement: '_')}\""
: "";
builder.AppendLineIndented($"[global::Foundation.Register({registerParamApple})]");
Expand All @@ -175,7 +174,7 @@ string GetGeneratedCode(INamedTypeSymbol typeSymbol)
{
builder.AppendLineIndented("#if __ANDROID__");
var registerParamAndroid = $"\"{typeSymbol.GetFullMetadataName().Replace(".", "/")}\"";
var registerParamAndroid = $"\"{typeSymbol.GetFullMetadataName(forRegisterAttributeDotReplacement: '/')}\"";
builder.AppendLineIndented($"[global::Android.Runtime.Register({registerParamAndroid})]");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using Android.Content;

namespace Uno.UI.RuntimeTests.Tests.NativeCtorGeneratorTests
{
public partial class GenericClass<T1, T2> : Android.Views.View
{
public GenericClass(Context context) : base(context) { }
public GenericClass(Context context, Android.Util.IAttributeSet attributeSet) : base(context, attributeSet) { }

public partial class NestedGenericClass<T3> : Android.Views.View
{
public NestedGenericClass(Context context) : base(context) { }
public NestedGenericClass(Context context, Android.Util.IAttributeSet attributeSet) : base(context, attributeSet) { }
}
}
}

public partial class GenericClassGlobalNamespace<T1, T2> : Android.Views.View
{
public GenericClassGlobalNamespace(Context context) : base(context) { }
public GenericClassGlobalNamespace(Context context, Android.Util.IAttributeSet attributeSet) : base(context, attributeSet) { }

public partial class NestedGenericClassGlobalNamespace<T3> : Android.Views.View
{
public NestedGenericClassGlobalNamespace(Context context) : base(context) { }
public NestedGenericClassGlobalNamespace(Context context, Android.Util.IAttributeSet attributeSet) : base(context, attributeSet) { }
}
}

0 comments on commit 2a17b6c

Please sign in to comment.