Skip to content

Commit

Permalink
[XAT.Bytecode] Bind Kotlin internal interfaces as package-private (#645)
Browse files Browse the repository at this point in the history
Context: dotnet/android#4661

In 439bd83, we made a change to hide Kotlin `internal` members as we
shouldn't be binding them.  However a `public class` can inherit from
an `internal interface`, so we need to emit those interfaces.

Instead, emit `internal interface` types as "package private" types;
`visibility=""` in `api.xml` parlance.  This allows us to resolve the
`interface` and thus successfully bind the `class`.  

In C#-land, the `interface` is not actually bound and the C# class
doesn't actually implement it.  This has no effect to the public API
end user, who shouldn't be seeing the `internal interface` anyways.
If a user really wants to publicly expose the `interface`, its
`visibility` can be changed via `metadata`.

***Note***: Changing the `visibility` of a type may result in
subsequent Java Callable Wrapper generation errors, if/when a
Java Callable Wrapper attempts to implement the package-private type.
  • Loading branch information
jpobst authored May 18, 2020
1 parent 186174c commit 1708d8a
Show file tree
Hide file tree
Showing 4 changed files with 26 additions and 0 deletions.
10 changes: 10 additions & 0 deletions src/Xamarin.Android.Tools.Bytecode/Kotlin/KotlinFixups.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,16 @@ static void FixupClassVisibility (ClassFile klass, KotlinClass metadata)
{
// Hide class if it isn't Public/Protected
if (klass.AccessFlags.IsPubliclyVisible () && !metadata.Visibility.IsPubliclyVisible ()) {

// Interfaces should be set to "package-private", which is "no visibility flags"
if (klass.AccessFlags.HasFlag (ClassAccessFlags.Interface)) {
Log.Debug ($"Kotlin: Setting interface {klass.ThisClass.Name.Value} to package-private");
klass.AccessFlags = (klass.AccessFlags ^ ClassAccessFlags.Public) & klass.AccessFlags;
klass.AccessFlags = (klass.AccessFlags ^ ClassAccessFlags.Protected) & klass.AccessFlags;
klass.AccessFlags = (klass.AccessFlags ^ ClassAccessFlags.Private) & klass.AccessFlags;
return;
}

Log.Debug ($"Kotlin: Hiding internal class {klass.ThisClass.Name.Value}");
klass.AccessFlags = ClassAccessFlags.Private;
return;
Expand Down
15 changes: 15 additions & 0 deletions tests/Xamarin.Android.Tools.Bytecode-Tests/KotlinFixupsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,21 @@ public void HideInternalClass ()
Assert.False (klass.AccessFlags.HasFlag (ClassAccessFlags.Public));
}

[Test]
public void MakeInternalInterfacePackagePrivate ()
{
var klass = LoadClassFile ("InternalInterface.class");

Assert.True (klass.AccessFlags.HasFlag (ClassAccessFlags.Public));

KotlinFixups.Fixup (new [] { klass });

// "package-private" is denoted as no visibility flags
Assert.False (klass.AccessFlags.HasFlag (ClassAccessFlags.Public));
Assert.False (klass.AccessFlags.HasFlag (ClassAccessFlags.Protected));
Assert.False (klass.AccessFlags.HasFlag (ClassAccessFlags.Private));
}

[Test]
public void HideInternalConstructor ()
{
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
internal interface InternalInterface

0 comments on commit 1708d8a

Please sign in to comment.