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

feat(dotnet): introduce UnsafeCast<T>() method #2192

Merged
merged 5 commits into from
Oct 28, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System;
using Amazon.JSII.Runtime.Deputy;
using Xunit;
using Xunit.Sdk;

namespace Amazon.JSII.Runtime.UnitTests.Deputy
{
public sealed class DeputyBaseTests
{
const string Prefix = "Runtime.Deputy." + nameof(DeputyBase) + ".";

[Fact(DisplayName = Prefix + nameof(CanCastToAnyInterface))]
public void CanCastToAnyInterface()
{
var subject = new AnonymousObject(new ByRefValue("object@10000", Array.Empty<string>()));
var result = subject.UnsafeCast<IManagedInterface>();
Assert.IsType<ManagedInterfaceProxy>(result);
}

[JsiiInterface(typeof(IManagedInterface), "test.IManagedInterface")]
private interface IManagedInterface
{
bool BooleanProperty { get; }
}

[JsiiTypeProxy(typeof(IManagedInterface), "test.IManagedInterface")]
private class ManagedInterfaceProxy : DeputyBase, IManagedInterface
{
public ManagedInterfaceProxy(ByRefValue byRef): base(byRef)
{
BooleanProperty = true;
}

public bool BooleanProperty { get; }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ namespace Amazon.JSII.Runtime.Deputy
{
internal sealed class AnonymousObject : DeputyBase
{
AnonymousObject(ByRefValue byRefValue) : base(byRefValue)
internal AnonymousObject(ByRefValue byRefValue) : base(byRefValue)
{
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security.Authentication.ExtendedProtection;
using Newtonsoft.Json;
using Type = System.Type;

namespace Amazon.JSII.Runtime.Deputy
{
Expand Down Expand Up @@ -127,7 +131,7 @@ protected static T GetStaticProperty<T>(System.Type type, [CallerMemberName] str
propertyName = propertyName ?? throw new ArgumentNullException(nameof(propertyName));

JsiiTypeAttributeBase.Load(type.Assembly);

var classAttribute = ReflectionUtils.GetClassAttribute(type)!;
var propertyAttribute = GetStaticPropertyAttribute(type, propertyName);

Expand Down Expand Up @@ -178,7 +182,7 @@ protected static void SetStaticProperty<T>(System.Type type, T value, [CallerMem
propertyName = propertyName ?? throw new ArgumentNullException(nameof(propertyName));

JsiiTypeAttributeBase.Load(type.Assembly);

var classAttribute = ReflectionUtils.GetClassAttribute(type)!;
var propertyAttribute = GetStaticPropertyAttribute(type, propertyName);

Expand Down Expand Up @@ -229,12 +233,12 @@ protected void InvokeInstanceVoidMethod(System.Type[] parameterTypes, object?[]
{
InvokeInstanceMethod<object>(parameterTypes, arguments, methodName);
}

[return: MaybeNull]
protected static T InvokeStaticMethod<T>(System.Type type, System.Type[] parameterTypes, object?[] arguments, [CallerMemberName] string methodName = "")
{
JsiiTypeAttributeBase.Load(type.Assembly);

var methodAttribute = GetStaticMethodAttribute(type, methodName, parameterTypes);
var classAttribute = ReflectionUtils.GetClassAttribute(type)!;

Expand Down Expand Up @@ -289,7 +293,7 @@ private static T InvokeMethodCore<T>(
{
throw new NotSupportedException($"Could not convert result '{result}' for method '{methodAttribute.Name}'");
}

return (T)frameworkValue!;

object? GetResult()
Expand Down Expand Up @@ -466,99 +470,137 @@ private static JsiiMethodAttribute GetMethodAttributeCore(System.Type type, stri
}

#endregion

#region IConvertible


/// <summary>
/// Unsafely obtains a proxy of a given type for this instance. This method allows obtaining a proxy instance
/// that is not known to be supported by the backing object instance; in which case the behavior of any
/// operation that is not supported by the backing instance is undefined.
/// </summary>
/// <typeparam name="T">
/// A jsii-managed interface to obtain a proxy for.
/// This interface must carry a <see cref="JsiiInterfaceAttribute" /> attribute.
/// </typeparam>
/// <returns>
/// An instance of <c>T</c>
/// </returns>
/// <exception cref="ArgumentException">
/// If the type provided for <c>T</c> does not carry the <see cref="JsiiInterfaceAttribute" /> attribute.
/// </exception>
public T UnsafeCast<T>() where T: class
{
if (this is T result)
{
return result;
}

try
{
return (T) Convert.ChangeType(this, typeof(T), CultureInfo.InvariantCulture);
}
catch (InvalidCastException)
{
// At this point, we are converting to a type that we don't know for sure is applicable
if (MakeProxy(typeof(T), true, out var proxy))
{
return (T)proxy;
}

throw;
}
}

private IDictionary<System.Type, object> Proxies { get; } = new Dictionary<System.Type, object>();

TypeCode IConvertible.GetTypeCode()
{
return TypeCode.Object;
}

object IConvertible.ToType(System.Type conversionType, IFormatProvider? provider)
{
if (Proxies.ContainsKey(conversionType))
{
return Proxies[conversionType];
}

if (ToTypeCore(out var converted))
{
return Proxies[conversionType] = converted!;
}

throw new InvalidCastException($"Unable to cast {this.GetType().FullName} into {conversionType.FullName}");

bool ToTypeCore(out object? result)
{
if (conversionType.IsInstanceOfType(this))
{
result = this;
return true;
}
if (!conversionType.IsInstanceOfType(this)) return MakeProxy(conversionType, false, out result);

if (!conversionType.IsInterface || Reference.Interfaces.Length == 0)
{
// We can only convert to interfaces that are declared on the Reference.
result = null;
return false;
}
result = this;
return true;

var interfaceAttribute = conversionType.GetCustomAttribute<JsiiInterfaceAttribute>();
if (interfaceAttribute == null)
{
// We can only convert to interfaces decorated with the JsiiInterfaceAttribute
result = null;
return false;
}
}
}

var types = ServiceContainer.ServiceProvider.GetRequiredService<ITypeCache>();

if (!TryFindSupportedInterface(interfaceAttribute.FullyQualifiedName, Reference.Interfaces, types, out var adequateFqn))
{
// We can only convert to interfaces declared by this Reference
result = null;
return false;
}

var proxyType = types.GetProxyType(interfaceAttribute.FullyQualifiedName);
var constructorInfo = proxyType.GetConstructor(
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
null,
new[] {typeof(ByRefValue)},
null
);
if (constructorInfo == null)
{
throw new JsiiException($"Could not find constructor to instantiate {proxyType.FullName}");
}
private bool MakeProxy(Type interfaceType, bool force, [NotNullWhen(true)] out object? result)
{
if (!interfaceType.IsInterface)
{
result = null;
return false;
}

var interfaceAttribute = interfaceType.GetCustomAttribute<JsiiInterfaceAttribute>();
if (interfaceAttribute == null)
{
// We can only convert to interfaces decorated with the JsiiInterfaceAttribute
result = null;
return false;
}

result = constructorInfo.Invoke(new object[]{ Reference.ForProxy() });
return true;
var types = ServiceContainer.ServiceProvider.GetRequiredService<ITypeCache>();

bool TryFindSupportedInterface(string declaredFqn, string[] availableFqns, ITypeCache types, out string? foundFqn)
{
var declaredType = types.GetInterfaceType(declaredFqn);
if (!TryFindSupportedInterface(interfaceAttribute.FullyQualifiedName, Reference.Interfaces, types, out var adequateFqn))
{
// We can only convert to interfaces declared by this Reference
result = null;
return false;
}

foreach (var candidate in availableFqns)
{
var candidateType = types.GetInterfaceType(candidate);
if (declaredType.IsAssignableFrom(candidateType))
{
foundFqn = candidate;
return true;
}
}

foundFqn = null;
return false;
var proxyType = types.GetProxyType(interfaceAttribute.FullyQualifiedName);
var constructorInfo = proxyType.GetConstructor(
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
null,
new[] {typeof(ByRefValue)},
null
);
if (constructorInfo == null)
{
throw new JsiiException($"Could not find constructor to instantiate {proxyType.FullName}");
}

result = constructorInfo.Invoke(new object[]{ Reference.ForProxy() });
return true;

bool TryFindSupportedInterface(string declaredFqn, string[] availableFqns, ITypeCache types, out string? foundFqn)
{
var declaredType = types.GetInterfaceType(declaredFqn);

foreach (var candidate in availableFqns)
{
var candidateType = types.GetInterfaceType(candidate);
if (!declaredType.IsAssignableFrom(candidateType)) continue;
foundFqn = candidate;
return true;
}

foundFqn = declaredFqn;
return force;
}
}

#region Impossible Conversions

bool IConvertible.ToBoolean(IFormatProvider? provider)
{
throw new InvalidCastException();
Expand All @@ -568,17 +610,17 @@ byte IConvertible.ToByte(IFormatProvider? provider)
{
throw new InvalidCastException();
}

char IConvertible.ToChar(IFormatProvider? provider)
{
throw new InvalidCastException();
}

DateTime IConvertible.ToDateTime(IFormatProvider? provider)
{
throw new InvalidCastException();
}

decimal IConvertible.ToDecimal(IFormatProvider? provider)
{
throw new InvalidCastException();
Expand Down Expand Up @@ -635,7 +677,7 @@ ulong IConvertible.ToUInt64(IFormatProvider? provider)
}

#endregion

#endregion
}
}