-
Notifications
You must be signed in to change notification settings - Fork 568
[TrimmableTypeMap][Core A] Foundation model and metadata providers #10816
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
Closed
simonrozsival
wants to merge
6
commits into
dotnet:main
from
simonrozsival:dev/simonrozsival/ttm-core-a-foundation
Closed
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
2ed1333
[TrimmableTypeMap][Core A] Foundation model + metadata type providers
simonrozsival 5ad9210
[TrimmableTypeMap][Core A] Prefer interpolation in scanner providers
simonrozsival 5d9a8b7
[TrimmableTypeMap][Core A] Use version variable for System.Reflection…
simonrozsival 70861e1
[TrimmableTypeMap][Core A] Address review feedback
simonrozsival e2f1c06
[TrimmableTypeMap][Core A] Bump System.Reflection.Metadata to 11.0.0-…
simonrozsival a2ac436
[TrimmableTypeMap][Core A] Use required init properties on records
simonrozsival File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
28 changes: 28 additions & 0 deletions
28
src/Microsoft.Android.Sdk.TrimmableTypeMap/CompilerFeaturePolyfills.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| // Polyfills for C# language features on netstandard2.0 | ||
|
|
||
| // Required for init-only setters | ||
| namespace System.Runtime.CompilerServices | ||
| { | ||
| static class IsExternalInit { } | ||
|
|
||
| [AttributeUsage (AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)] | ||
| sealed class RequiredMemberAttribute : Attribute { } | ||
|
|
||
| [AttributeUsage (AttributeTargets.All, AllowMultiple = true, Inherited = false)] | ||
| sealed class CompilerFeatureRequiredAttribute : Attribute | ||
| { | ||
| public CompilerFeatureRequiredAttribute (string featureName) | ||
| { | ||
| FeatureName = featureName; | ||
| } | ||
|
|
||
| public string FeatureName { get; } | ||
| public bool IsOptional { get; init; } | ||
| } | ||
| } | ||
|
|
||
| namespace System.Diagnostics.CodeAnalysis | ||
| { | ||
| [AttributeUsage (AttributeTargets.Constructor, AllowMultiple = false, Inherited = false)] | ||
| sealed class SetsRequiredMembersAttribute : Attribute { } | ||
| } |
18 changes: 18 additions & 0 deletions
18
src/Microsoft.Android.Sdk.TrimmableTypeMap/Microsoft.Android.Sdk.TrimmableTypeMap.csproj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
| <Import Project="..\..\Configuration.props" /> | ||
|
|
||
| <PropertyGroup> | ||
| <TargetFramework>$(TargetFrameworkNETStandard)</TargetFramework> | ||
| <Nullable>enable</Nullable> | ||
| <WarningsAsErrors>Nullable</WarningsAsErrors> | ||
| <RootNamespace>Microsoft.Android.Sdk.TrimmableTypeMap</RootNamespace> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <PackageReference Include="System.IO.Hashing" Version="$(SystemIOHashingPackageVersion)" /> | ||
| <PackageReference Include="System.Reflection.Metadata" Version="$(SystemReflectionMetadataPackageVersion)" /> | ||
| <InternalsVisibleTo Include="Microsoft.Android.Sdk.TrimmableTypeMap.Tests" /> | ||
| <InternalsVisibleTo Include="Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests" /> | ||
| </ItemGroup> | ||
|
|
||
| </Project> |
127 changes: 127 additions & 0 deletions
127
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/CustomAttributeTypeProvider.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,127 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Reflection.Metadata; | ||
|
|
||
| namespace Microsoft.Android.Sdk.TrimmableTypeMap; | ||
|
|
||
| /// <summary> | ||
| /// Minimal ICustomAttributeTypeProvider implementation for decoding | ||
| /// custom attribute values via System.Reflection.Metadata. | ||
| /// </summary> | ||
| sealed class CustomAttributeTypeProvider : ICustomAttributeTypeProvider<string> | ||
| { | ||
| readonly MetadataReader reader; | ||
| Dictionary<string, PrimitiveTypeCode>? enumTypeCache; | ||
|
|
||
| public CustomAttributeTypeProvider (MetadataReader reader) | ||
| { | ||
| this.reader = reader; | ||
| } | ||
|
|
||
| public string GetPrimitiveType (PrimitiveTypeCode typeCode) => typeCode.ToString (); | ||
|
|
||
| public string GetTypeFromDefinition (MetadataReader metadataReader, TypeDefinitionHandle handle, byte rawTypeKind) | ||
| { | ||
| var typeDef = metadataReader.GetTypeDefinition (handle); | ||
| var name = metadataReader.GetString (typeDef.Name); | ||
| if (typeDef.IsNested) { | ||
| var parent = GetTypeFromDefinition (metadataReader, typeDef.GetDeclaringType (), rawTypeKind); | ||
| return $"{parent}+{name}"; | ||
| } | ||
| var ns = metadataReader.GetString (typeDef.Namespace); | ||
| return ns.Length > 0 ? $"{ns}.{name}" : name; | ||
| } | ||
|
|
||
| public string GetTypeFromReference (MetadataReader metadataReader, TypeReferenceHandle handle, byte rawTypeKind) | ||
| { | ||
| var typeRef = metadataReader.GetTypeReference (handle); | ||
| var name = metadataReader.GetString (typeRef.Name); | ||
| if (typeRef.ResolutionScope.Kind == HandleKind.TypeReference) { | ||
| var parent = GetTypeFromReference (metadataReader, (TypeReferenceHandle)typeRef.ResolutionScope, rawTypeKind); | ||
| return $"{parent}+{name}"; | ||
| } | ||
| var ns = metadataReader.GetString (typeRef.Namespace); | ||
| return ns.Length > 0 ? $"{ns}.{name}" : name; | ||
| } | ||
|
|
||
| public string GetTypeFromSerializedName (string name) => name; | ||
|
|
||
| public PrimitiveTypeCode GetUnderlyingEnumType (string type) | ||
| { | ||
| if (enumTypeCache == null) { | ||
| enumTypeCache = BuildEnumTypeCache (); | ||
| } | ||
|
|
||
| if (enumTypeCache.TryGetValue (type, out var code)) { | ||
| return code; | ||
| } | ||
|
|
||
| // Default to Int32 for enums defined in other assemblies | ||
| return PrimitiveTypeCode.Int32; | ||
| } | ||
|
|
||
| Dictionary<string, PrimitiveTypeCode> BuildEnumTypeCache () | ||
| { | ||
| var cache = new Dictionary<string, PrimitiveTypeCode> (); | ||
|
|
||
| foreach (var typeHandle in reader.TypeDefinitions) { | ||
| var typeDef = reader.GetTypeDefinition (typeHandle); | ||
|
|
||
| // Only process enum types | ||
| if (!IsEnum (typeDef)) | ||
| continue; | ||
|
|
||
| var fullName = GetTypeFromDefinition (reader, typeHandle, rawTypeKind: 0); | ||
| var code = GetEnumUnderlyingTypeCode (typeDef); | ||
| cache [fullName] = code; | ||
| } | ||
|
|
||
| return cache; | ||
| } | ||
|
|
||
| bool IsEnum (TypeDefinition typeDef) | ||
| { | ||
| var baseType = typeDef.BaseType; | ||
| if (baseType.IsNil) | ||
| return false; | ||
|
|
||
| string? baseFullName = baseType.Kind switch { | ||
| HandleKind.TypeReference => GetTypeFromReference (reader, (TypeReferenceHandle)baseType, rawTypeKind: 0), | ||
| HandleKind.TypeDefinition => GetTypeFromDefinition (reader, (TypeDefinitionHandle)baseType, rawTypeKind: 0), | ||
| _ => null, | ||
| }; | ||
|
|
||
| return baseFullName == "System.Enum"; | ||
| } | ||
|
|
||
| PrimitiveTypeCode GetEnumUnderlyingTypeCode (TypeDefinition typeDef) | ||
| { | ||
| // For enums, the first instance field is the underlying value__ field | ||
| foreach (var fieldHandle in typeDef.GetFields ()) { | ||
| var field = reader.GetFieldDefinition (fieldHandle); | ||
| if ((field.Attributes & System.Reflection.FieldAttributes.Static) != 0) | ||
| continue; | ||
|
|
||
| var sig = field.DecodeSignature (SignatureTypeProvider.Instance, genericContext: null); | ||
| return sig switch { | ||
| "System.Byte" => PrimitiveTypeCode.Byte, | ||
| "System.SByte" => PrimitiveTypeCode.SByte, | ||
| "System.Int16" => PrimitiveTypeCode.Int16, | ||
| "System.UInt16" => PrimitiveTypeCode.UInt16, | ||
| "System.Int32" => PrimitiveTypeCode.Int32, | ||
| "System.UInt32" => PrimitiveTypeCode.UInt32, | ||
| "System.Int64" => PrimitiveTypeCode.Int64, | ||
| "System.UInt64" => PrimitiveTypeCode.UInt64, | ||
| _ => PrimitiveTypeCode.Int32, | ||
| }; | ||
| } | ||
|
|
||
| return PrimitiveTypeCode.Int32; | ||
| } | ||
|
|
||
| public string GetSystemType () => "System.Type"; | ||
|
|
||
| public string GetSZArrayType (string elementType) => $"{elementType}[]"; | ||
|
|
||
| public bool IsSystemType (string type) => type == "System.Type" || type == "Type"; | ||
| } | ||
173 changes: 173 additions & 0 deletions
173
src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,173 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
|
|
||
| namespace Microsoft.Android.Sdk.TrimmableTypeMap; | ||
|
|
||
| /// <summary> | ||
| /// Represents a Java peer type discovered during assembly scanning. | ||
| /// Contains all data needed by downstream generators (TypeMap IL, UCO wrappers, JCW Java sources). | ||
| /// Generators consume this data model — they never touch PEReader/MetadataReader. | ||
| /// </summary> | ||
| sealed record JavaPeerInfo | ||
| { | ||
| /// <summary> | ||
| /// JNI type name, e.g., "android/app/Activity". | ||
| /// Extracted from the [Register] attribute. | ||
| /// </summary> | ||
| public required string JavaName { get; init; } | ||
|
|
||
| /// <summary> | ||
| /// Compat JNI type name, e.g., "myapp.namespace/MyType" for user types (uses raw namespace, not CRC64). | ||
| /// For MCW binding types (with [Register]), this equals <see cref="JavaName"/>. | ||
| /// Used by acw-map.txt to support legacy custom view name resolution in layout XMLs. | ||
| /// </summary> | ||
| public required string CompatJniName { get; init; } | ||
|
|
||
| /// <summary> | ||
| /// Full managed type name, e.g., "Android.App.Activity". | ||
| /// </summary> | ||
| public required string ManagedTypeName { get; init; } | ||
|
|
||
| /// <summary> | ||
| /// Assembly name the type belongs to, e.g., "Mono.Android". | ||
| /// </summary> | ||
| public required string AssemblyName { get; init; } | ||
|
|
||
| /// <summary> | ||
| /// JNI name of the base Java type, e.g., "android/app/Activity" for a type | ||
| /// that extends Activity. Null for java/lang/Object or types without a Java base. | ||
| /// Needed by JCW Java source generation ("extends" clause). | ||
| /// </summary> | ||
| public string? BaseJavaName { get; init; } | ||
|
|
||
| /// <summary> | ||
| /// JNI names of Java interfaces this type implements, e.g., ["android/view/View$OnClickListener"]. | ||
| /// Needed by JCW Java source generation ("implements" clause). | ||
| /// </summary> | ||
| public IReadOnlyList<string> ImplementedInterfaceJavaNames { get; init; } = Array.Empty<string> (); | ||
|
|
||
| public bool IsInterface { get; init; } | ||
| public bool IsAbstract { get; init; } | ||
|
|
||
| /// <summary> | ||
| /// If true, this is a Managed Callable Wrapper (MCW) binding type. | ||
| /// No JCW or RegisterNatives will be generated for it. | ||
| /// </summary> | ||
| public bool DoNotGenerateAcw { get; init; } | ||
|
|
||
| /// <summary> | ||
| /// Types with component attributes ([Activity], [Service], etc.), | ||
| /// custom views from layout XML, or manifest-declared components | ||
| /// are unconditionally preserved (not trimmable). | ||
| /// </summary> | ||
| public bool IsUnconditional { get; init; } | ||
|
|
||
| /// <summary> | ||
| /// Marshal methods: methods with [Register(name, sig, connector)], [Export], or | ||
| /// constructor registrations ([Register(".ctor", sig, "")] / [JniConstructorSignature]). | ||
| /// Constructors are identified by <see cref="MarshalMethodInfo.IsConstructor"/>. | ||
| /// Ordered — the index in this list is the method's ordinal for RegisterNatives. | ||
| /// </summary> | ||
| public IReadOnlyList<MarshalMethodInfo> MarshalMethods { get; init; } = Array.Empty<MarshalMethodInfo> (); | ||
|
|
||
| /// <summary> | ||
| /// Information about the activation constructor for this type. | ||
| /// May reference a base type's constructor if the type doesn't define its own. | ||
| /// </summary> | ||
| public ActivationCtorInfo? ActivationCtor { get; init; } | ||
|
|
||
| /// <summary> | ||
| /// For interfaces and abstract types, the name of the invoker type | ||
| /// used to instantiate instances from Java. | ||
| /// </summary> | ||
| public string? InvokerTypeName { get; init; } | ||
|
|
||
| /// <summary> | ||
| /// True if this is an open generic type definition. | ||
| /// Generic types get TypeMap entries but CreateInstance throws NotSupportedException. | ||
| /// </summary> | ||
| public bool IsGenericDefinition { get; init; } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Describes a marshal method (a method with [Register] or [Export]) on a Java peer type. | ||
| /// Contains all data needed to generate a UCO wrapper, a JCW native declaration, | ||
| /// and a RegisterNatives call. | ||
| /// </summary> | ||
| sealed record MarshalMethodInfo | ||
| { | ||
| /// <summary> | ||
| /// JNI method name, e.g., "onCreate". | ||
| /// This is the Java method name (without n_ prefix). | ||
| /// </summary> | ||
| public required string JniName { get; init; } | ||
|
|
||
| /// <summary> | ||
| /// JNI method signature, e.g., "(Landroid/os/Bundle;)V". | ||
| /// Contains both parameter types and return type. | ||
| /// </summary> | ||
| public required string JniSignature { get; init; } | ||
|
|
||
| /// <summary> | ||
| /// The connector string from [Register], e.g., "GetOnCreate_Landroid_os_Bundle_Handler". | ||
| /// Null for [Export] methods. | ||
| /// </summary> | ||
| public string? Connector { get; init; } | ||
|
|
||
| /// <summary> | ||
| /// Name of the managed method this maps to, e.g., "OnCreate". | ||
| /// </summary> | ||
| public required string ManagedMethodName { get; init; } | ||
|
|
||
| /// <summary> | ||
| /// True if this is a constructor registration. | ||
| /// </summary> | ||
| public bool IsConstructor { get; init; } | ||
|
|
||
| /// <summary> | ||
| /// For [Export] methods: Java exception types that the method declares it can throw. | ||
| /// Null for [Register] methods. | ||
| /// </summary> | ||
| public IReadOnlyList<string>? ThrownNames { get; init; } | ||
|
|
||
| /// <summary> | ||
| /// For [Export] methods: super constructor arguments string. | ||
| /// Null for [Register] methods. | ||
| /// </summary> | ||
| public string? SuperArgumentsString { get; init; } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Describes how to call the activation constructor for a Java peer type. | ||
| /// </summary> | ||
| sealed record ActivationCtorInfo | ||
| { | ||
| /// <summary> | ||
| /// The type that declares the activation constructor. | ||
| /// May be the type itself or a base type. | ||
| /// </summary> | ||
| public required string DeclaringTypeName { get; init; } | ||
|
|
||
| /// <summary> | ||
| /// The assembly containing the declaring type. | ||
| /// </summary> | ||
| public required string DeclaringAssemblyName { get; init; } | ||
|
|
||
| /// <summary> | ||
| /// The style of activation constructor found. | ||
| /// </summary> | ||
| public required ActivationCtorStyle Style { get; init; } | ||
| } | ||
|
|
||
| enum ActivationCtorStyle | ||
| { | ||
| /// <summary> | ||
| /// Xamarin.Android style: (IntPtr handle, JniHandleOwnership transfer) | ||
| /// </summary> | ||
| XamarinAndroid, | ||
|
|
||
| /// <summary> | ||
| /// Java.Interop style: (ref JniObjectReference reference, JniObjectReferenceOptions options) | ||
| /// </summary> | ||
| JavaInterop, | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.