Skip to content

Commit

Permalink
Add methods for finding methods that implement interface methods.
Browse files Browse the repository at this point in the history
  • Loading branch information
jpobst committed Jul 6, 2023
1 parent b15749b commit 2fb41d5
Show file tree
Hide file tree
Showing 17 changed files with 536 additions and 61 deletions.
48 changes: 48 additions & 0 deletions src/Javil/Extensions/TypeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

static class TypeExtensions
{
static readonly GenericParameterMapping no_mapping = new GenericParameterMapping ();

public static string PrimitiveTypeToName (string type)
{
return type switch {
Expand All @@ -17,4 +19,50 @@ public static string PrimitiveTypeToName (string type)
_ => throw new NotImplementedException (),
};
}

public static bool AreMethodsCompatible (MethodDefinition method, MethodDefinition candidate, GenericParameterMapping mapping)
{
for (var i = 0; i < method.Parameters.Count; i++) {
if (!AreParametersCompatible (method.Parameters[i].ParameterType, candidate.Parameters[i].ParameterType, mapping))
return false;
}

return true;
}

public static bool AreParametersCompatible (TypeReference type1, TypeReference type2, GenericParameterMapping? mapping = null)
{
mapping ??= no_mapping;

if (type1.ToString () == mapping.GetMappedReference (type2).ToString ())
return true;

// TODO: Is this ok?
// It catches cases like:
// - java.lang.Class[] = java.lang.Class<?>[]
// - java.util.Map<**> = java.util.Map<java.lang.Object, java.lang.Object>
if (type1.JniFullNameGenericsErased == type2.JniFullNameGenericsErased)
return true;

return false;
}

public static bool ImplementsInterface (this TypeDefinition type, TypeDefinition iface)
{
if (!iface.IsInterface)
throw new ArgumentException ($"'{iface.FullName}' is not an interface.");

foreach (var i in type.ImplementedInterfaces) {
if (i.InterfaceType.FullNameGenericsErased == iface.FullNameGenericsErased)
return true;

// Recurse through interfaces
if (i.InterfaceType.Resolve () is TypeDefinition td && td.ImplementsInterface (iface))
return true;
}

// TODO: Should this check base types?

return false;
}
}
2 changes: 1 addition & 1 deletion src/Javil/GenericParameterMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public TypeReference GetMappedReference (TypeReference typeReference)
return typeReference;
}

public string GetMapping (TypeReference typeReference)
private string GetMapping (TypeReference typeReference)
{
if (typeReference is GenericParameter gp) {
var mapping = GetMapping (gp.Name);
Expand Down
112 changes: 53 additions & 59 deletions src/Javil/MethodDefinition.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text;
using Javil.Extensions;

namespace Javil;

Expand Down Expand Up @@ -38,6 +39,32 @@ public MethodDefinition (string name, TypeReference returnType, TypeReference de
ReturnType = returnType;
}

/// <summary>
/// Finds the immediate base method this method overrides, if any.
/// Example: Given 'Dog.Eat ()' -> 'Mammal.Eat ()' -> 'Animal.Eat ()'.
/// Calling this for 'Dog.Eat ()' would return 'Mammal.Eat ()'.
/// </summary>
public MethodDefinition? FindBaseMethodOrDefault ()
{
// Static methods cannot be overridden
if (IsStatic)
return null;

if (DeclaringType?.Resolve ()?.BaseType is TypeReference base_type) {
var mapping = new GenericParameterMapping ();
mapping.AddMappingFromTypeReference (base_type);

return FindBaseMethod (base_type.Resolve (), mapping);
}

return null;
}

/// <summary>
/// Finds the original 'virtual' method this method overrides, if any.
/// Example: Given 'Dog.Eat ()' -> 'Mammal.Eat ()' -> 'Animal.Eat ()'.
/// Calling this for 'Dog.Eat ()' would return 'Animal.Eat ()'.
/// </summary>
public MethodDefinition? FindDeclaredBaseMethodOrDefault ()
{
var candidate = FindBaseMethodOrDefault ();
Expand All @@ -55,22 +82,6 @@ public MethodDefinition (string name, TypeReference returnType, TypeReference de
}
}

public MethodDefinition? FindBaseMethodOrDefault ()
{
// Static methods cannot be overridden
if (IsStatic)
return null;

if (DeclaringType?.Resolve ()?.BaseType is TypeReference base_type) {
var mapping = new GenericParameterMapping ();
mapping.AddMappingFromTypeReference (base_type);

return FindBaseMethod (base_type.Resolve (), mapping);
}

return null;
}

public virtual IEnumerable<GenericParameter> GetGenericParametersInScope ()
{
if (HasGenericParameters)
Expand All @@ -82,25 +93,6 @@ public virtual IEnumerable<GenericParameter> GetGenericParametersInScope ()
yield return gp;
}

private MethodDefinition? FindBaseMethod (TypeDefinition? type, GenericParameterMapping mapping)
{
if (type is null)
return null;

var candidates = type.Methods.OfType<MethodDefinition> ().Where (m => m.Name == Name && m.Parameters.Count == Parameters.Count);

foreach (var candidate in candidates)
if (AreMethodsCompatible (this, candidate, mapping))
return candidate;

if (type.BaseType is TypeReference base_type) {
mapping.AddMappingFromTypeReference (base_type);
return FindBaseMethod (base_type.Resolve (), mapping);
}

return null;
}

protected override IMemberDefinition? ResolveDefinition ()
{
return this;
Expand All @@ -122,30 +114,8 @@ public override string GenericName {
}
}

private bool AreMethodsCompatible (MethodDefinition method, MethodDefinition candidate, GenericParameterMapping mapping)
{
for (var i = 0; i < method.Parameters.Count; i++) {
var p1 = method.Parameters[i].ParameterType.FullName;
var p2 = mapping.GetMapping (candidate.Parameters[i].ParameterType);

if (p1 == p2)
continue;

// TODO: Need a more elegant/correct way of handling the following cases
if (p1.Replace ("**", "java.lang.Object, java.lang.Object") == p2)
continue;

if (p1.Replace ("?", "java.lang.Object") == p2)
continue;

if (p1.Replace ("java.lang.Class<?>", "java.lang.Class") == p2.Replace ("java.lang.Class<?>", "java.lang.Class"))
continue;

return false;
}

return true;
}
public string GetDescriptorGenericsErased ()
=> $"({string.Join ("", Parameters.Select (p => p.ParameterType.JniFullNameGenericsErased))}){ReturnType.JniFullNameGenericsErased}";

// (Landroid/content/ComponentName;Ljava/lang/String;Ljava/util/List;)Z
public string GetDescriptor ()
Expand Down Expand Up @@ -173,4 +143,28 @@ public override string ToString ()
{
return $"{ReturnType.Name} {FullName} ({string.Join (", ", Parameters.Select (p => $"{p.ParameterType.Name} {p.Name}"))})";
}

private MethodDefinition? FindBaseMethod (TypeDefinition? type, GenericParameterMapping mapping)
{
if (type is null)
return null;

var candidates = type.Methods.OfType<MethodDefinition> ().Where (m => m.Name == Name && m.Parameters.Count == Parameters.Count);

foreach (var candidate in candidates)
if (TypeExtensions.AreMethodsCompatible (this, candidate, mapping))
return candidate;

if (type.BaseType is TypeReference base_type) {
mapping.AddMappingFromTypeReference (base_type);
return FindBaseMethod (base_type.Resolve (), mapping);
}

return null;
}

public bool IsMethodCovariantReturn (MethodDefinition candidate, GenericParameterMapping? mapping = null)
{
return !TypeExtensions.AreParametersCompatible (ReturnType, candidate.ReturnType, mapping);
}
}
151 changes: 150 additions & 1 deletion src/Javil/TypeDefinition.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using Javil.Attributes;
using System.Diagnostics.CodeAnalysis;
using Javil.Attributes;
using Javil.Extensions;

namespace Javil;

Expand Down Expand Up @@ -47,4 +49,151 @@ public TypeDefinition (string @namespace, string name, TypeReference? declaringT
public bool IsDeprecated { get; set; }

public bool HasAttributes => attributes?.Any () == true;

/// <summary>
/// Finds the concrete method that implements the requested interface method declaration.
/// Example: Given the hierarchy 'MyClass : Clonable<MyClass>', returns 'MyClass.Clone ()'
/// for the implemented interface 'Clonable<MyClass>' and method 'Clone ()'.
/// </summary>
public MethodDefinition? FindImplementedInterfaceOrDefault (ImplementedInterface iface, MethodDefinition method)
{
// Static methods cannot be implemented
if (method.IsStatic)
return null;

var mapping = new GenericParameterMapping ();
mapping.AddMappingFromTypeReference (iface.InterfaceType);

return FindImplementedMethodInClass (this, method, mapping);
}

private MethodDefinition? FindImplementedMethodInClass (TypeDefinition? type, MethodDefinition method, GenericParameterMapping mapping)
{
if (type is null)
return null;

var candidates = type.Methods.OfType<MethodDefinition> ().Where (m => m.Name == method.Name && m.Parameters.Count == method.Parameters.Count);

foreach (var candidate in candidates)
if (TypeExtensions.AreMethodsCompatible (candidate, method, mapping))
return candidate;

// Check if any base types have implemented the method
if (type.BaseType is TypeReference base_type) {
// Make a clone so we can have a clean mapping later
var base_mapping = mapping.Clone ();
base_mapping.AddMappingFromTypeReference (base_type);

if (FindImplementedMethodInClass (base_type.Resolve (), method, base_mapping) is MethodDefinition md)
return md;
}

if (method.DeclaringType?.Resolve () is TypeDefinition declaring_interface) {
// Check if any other implemented interfaces implement this one and provides a default method of it
foreach (var ii in type.ImplementedInterfaces) {
var iface_mapping = mapping.Clone ();
iface_mapping.AddMappingFromTypeReference (ii.InterfaceType);

if (ii.InterfaceType.Resolve () is TypeDefinition iface && iface.ImplementsInterface (declaring_interface)) {
if (FindImplementedMethodInInterface (iface, method, iface_mapping) is MethodDefinition md)
return md;
}
}

}

return null;
}

private MethodDefinition? FindImplementedMethodInInterface (TypeDefinition? type, MethodDefinition method, GenericParameterMapping mapping)
{
if (type is null)
return null;

var candidates = type.Methods.OfType<MethodDefinition> ().Where (m => !m.IsAbstract && m.Name == method.Name && m.Parameters.Count == method.Parameters.Count);

foreach (var candidate in candidates)
if (TypeExtensions.AreMethodsCompatible (candidate, method, mapping))
return candidate;

foreach (var iface in type.ImplementedInterfaces)
if (FindImplementedMethodInInterface (iface.InterfaceType.Resolve (), method, mapping) is MethodDefinition md)
return md;

return null;
}

/// <summary>
/// If this interface method is a default method, it may be implementing a method declaration
/// from an "implemented interface".
/// Example:
/// interface Describable { String Describe (); }
/// interface Animal implements Describable { String Describe () { return "Animal"; } }
/// 'Animal.Describe' would return 'Describable.Describe' since it provides an implementation for the method declaration.
/// </summary>
public bool TryFindDeclarationMethodIsProvidingImplementationFor (MethodDefinition method, [NotNullWhen (true)] out ImplementedInterface? implementedInterface, [NotNullWhen (true)] out MethodDefinition? implementedMethod)
{
// TODO: Needs unit tests
implementedInterface = null;
implementedMethod = null;

// If the method isn't providing an implementation of anything, return null
if (method.IsAbstract)
return false;

// TODO: Ensure this method is part of this interface

// See if there is an abstract version of this method on this interface
foreach (var ii in ImplementedInterfaces) {
if (ii.InterfaceType.Resolve () is TypeDefinition type) {
var mapping = new GenericParameterMapping ();
mapping.AddMappingFromTypeReference (ii.InterfaceType);

if (TryFindDeclarationMethodIsProvidingImplementationForCore (ii, method, mapping, out var foundIface, out var md)) {
implementedInterface = foundIface;
implementedMethod = md;
return true;
}
}
}

return false;
}

bool TryFindDeclarationMethodIsProvidingImplementationForCore (ImplementedInterface iface, MethodDefinition method, GenericParameterMapping mapping, [NotNullWhen (true)] out ImplementedInterface? implementedInterface, [NotNullWhen (true)] out MethodDefinition? implementedMethod)
{
implementedInterface = null;
implementedMethod = null;

// See if there is an abstract version of this method on this interface
if (iface.InterfaceType.Resolve () is TypeDefinition type) {

// Make a clone so don't affect parent usage
var new_mapping = mapping.Clone ();
new_mapping.AddMappingFromTypeReference (iface.InterfaceType);

if (type.Name == "Temporal" && method.Name == "isSupported")
Console.WriteLine ();

// Look for method
var candidates = type.Methods.OfType<MethodDefinition> ().Where (m => m.IsAbstract && m.Name == method.Name && m.Parameters.Count == method.Parameters.Count);

foreach (var candidate in candidates)
if (TypeExtensions.AreMethodsCompatible (method, candidate, mapping)) {
implementedInterface = iface;
implementedMethod = candidate;
return true;
}

// Recurse into implemented interfaces
foreach (var i in type.ImplementedInterfaces)
if (TryFindDeclarationMethodIsProvidingImplementationForCore (i, method, mapping, out var ii, out var md)) {
implementedInterface = ii;
implementedMethod = md;
return true;
}
}

return false;
}
}
Loading

0 comments on commit 2fb41d5

Please sign in to comment.