Skip to content

Commit

Permalink
fix(dotnet): allow down-casting to parent interface type
Browse files Browse the repository at this point in the history
Cast controls were too strict and prevented the framework from
successfully down-casting an object reference to a parent interface of
it's declared type; causing a cast error.

The new code looks for class compatibility using the standard .NET
primitives and successfully performs the requested cast.

Fixes #982
  • Loading branch information
RomainMuller committed Nov 13, 2019
1 parent 948000d commit c977e34
Show file tree
Hide file tree
Showing 6 changed files with 209 additions and 21 deletions.
30 changes: 30 additions & 0 deletions packages/jsii-calc/lib/compliance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2208,3 +2208,33 @@ export class RootStructValidator {

private constructor() { }
}

/** https://github.com/aws/jsii/issues/982 */
export interface ParentStruct982 {
readonly foo: string;
}
export interface ChildStruct982 extends ParentStruct982 {
readonly bar: number;
}
/**
* 1. call #takeThis() -> An ObjectRef will be provisioned for the value (it'll be re-used!)
* 2. call #takeThisToo() -> The ObjectRef from before will need to be down-cased to the ParentStruct982 type
*/
export class Demonstrate982 {
private static readonly value = {
foo: 'foo',
bar: 1337,
};

/** It's dangerous to go alone! */
public static takeThis(): ChildStruct982 {
return this.value;
}

/** It's dangerous to go alone! */
public static takeThisToo(): ParentStruct982 {
return this.value;
}

public constructor() { }
}
124 changes: 123 additions & 1 deletion packages/jsii-calc/test/assembly.jsii
Original file line number Diff line number Diff line change
Expand Up @@ -1664,6 +1664,40 @@
}
]
},
"jsii-calc.ChildStruct982": {
"assembly": "jsii-calc",
"datatype": true,
"docs": {
"stability": "experimental"
},
"fqn": "jsii-calc.ChildStruct982",
"interfaces": [
"jsii-calc.ParentStruct982"
],
"kind": "interface",
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 2216
},
"name": "ChildStruct982",
"properties": [
{
"abstract": true,
"docs": {
"stability": "experimental"
},
"immutable": true,
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 2217
},
"name": "bar",
"type": {
"primitive": "number"
}
}
]
},
"jsii-calc.ClassThatImplementsTheInternalInterface": {
"assembly": "jsii-calc",
"docs": {
Expand Down Expand Up @@ -2811,6 +2845,62 @@
}
]
},
"jsii-calc.Demonstrate982": {
"assembly": "jsii-calc",
"docs": {
"remarks": "call #takeThis() -> An ObjectRef will be provisioned for the value (it'll be re-used!)\n2. call #takeThisToo() -> The ObjectRef from before will need to be down-cased to the ParentStruct982 type",
"stability": "experimental",
"summary": "1."
},
"fqn": "jsii-calc.Demonstrate982",
"initializer": {
"docs": {
"stability": "experimental"
}
},
"kind": "class",
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 2223
},
"methods": [
{
"docs": {
"stability": "experimental",
"summary": "It's dangerous to go alone!"
},
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 2230
},
"name": "takeThis",
"returns": {
"type": {
"fqn": "jsii-calc.ChildStruct982"
}
},
"static": true
},
{
"docs": {
"stability": "experimental",
"summary": "It's dangerous to go alone!"
},
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 2235
},
"name": "takeThisToo",
"returns": {
"type": {
"fqn": "jsii-calc.ParentStruct982"
}
},
"static": true
}
],
"name": "Demonstrate982"
},
"jsii-calc.DeprecatedClass": {
"assembly": "jsii-calc",
"docs": {
Expand Down Expand Up @@ -7604,6 +7694,38 @@
],
"name": "OverrideReturnsObject"
},
"jsii-calc.ParentStruct982": {
"assembly": "jsii-calc",
"datatype": true,
"docs": {
"stability": "experimental",
"summary": "https://github.com/aws/jsii/issues/982."
},
"fqn": "jsii-calc.ParentStruct982",
"kind": "interface",
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 2213
},
"name": "ParentStruct982",
"properties": [
{
"abstract": true,
"docs": {
"stability": "experimental"
},
"immutable": true,
"locationInModule": {
"filename": "lib/compliance.ts",
"line": 2214
},
"name": "foo",
"type": {
"primitive": "string"
}
}
]
},
"jsii-calc.PartiallyInitializedThisConsumer": {
"abstract": true,
"assembly": "jsii-calc",
Expand Down Expand Up @@ -10913,5 +11035,5 @@
}
},
"version": "0.20.3",
"fingerprint": "1F+uskR3++T5mjRcWge9oG3H/jJvXm1C3IhR1AwsBTE="
"fingerprint": "OKPyEfqSbC1Z0sgAKDwTTG8RRdQtwnKPf+OZQllfVUM="
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,8 @@
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\jsii-dotnet-runtime\src\Amazon.JSII.Runtime\Amazon.JSII.Runtime.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -1251,5 +1251,12 @@ public double Next()
return next;
}
}

[Fact(DisplayName = Prefix + nameof(StructsCanBeDowncastedToParentType))]
public void StructsCanBeDowncastedToParentType()
{
Assert.NotNull(Demonstrate982.TakeThis());
Assert.NotNull(Demonstrate982.TakeThisToo());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -455,12 +455,12 @@ private static JsiiMethodAttribute GetMethodAttributeCore(System.Type type, stri

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

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

public object ToType(System.Type conversionType, IFormatProvider provider)
object IConvertible.ToType(System.Type conversionType, IFormatProvider provider)
{
if (Proxies.ContainsKey(conversionType))
{
Expand Down Expand Up @@ -498,14 +498,15 @@ bool ToTypeCore(out object result)
return false;
}

if (!Reference.Interfaces.Contains(interfaceAttribute.FullyQualifiedName))
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 types = ServiceContainer.ServiceProvider.GetRequiredService<ITypeCache>();

var proxyType = types.GetProxyType(interfaceAttribute.FullyQualifiedName);
var constructorInfo = proxyType.GetConstructor(
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
Expand All @@ -520,82 +521,100 @@ bool ToTypeCore(out object result)

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))
{
foundFqn = candidate;
return true;
}
}

foundFqn = null;
return false;
}
}
}

#region Impossible Conversions

public bool ToBoolean(IFormatProvider provider)
bool IConvertible.ToBoolean(IFormatProvider provider)
{
throw new InvalidCastException();
}

public byte ToByte(IFormatProvider provider)
byte IConvertible.ToByte(IFormatProvider provider)
{
throw new InvalidCastException();
}

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

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

public decimal ToDecimal(IFormatProvider provider)
decimal IConvertible.ToDecimal(IFormatProvider provider)
{
throw new InvalidCastException();
}

public double ToDouble(IFormatProvider provider)
double IConvertible.ToDouble(IFormatProvider provider)
{
throw new InvalidCastException();
}

public short ToInt16(IFormatProvider provider)
short IConvertible.ToInt16(IFormatProvider provider)
{
throw new InvalidCastException();
}

public int ToInt32(IFormatProvider provider)
int IConvertible.ToInt32(IFormatProvider provider)
{
throw new InvalidCastException();
}

public long ToInt64(IFormatProvider provider)
long IConvertible.ToInt64(IFormatProvider provider)
{
throw new InvalidCastException();
}

public sbyte ToSByte(IFormatProvider provider)
sbyte IConvertible.ToSByte(IFormatProvider provider)
{
throw new InvalidCastException();
}

public float ToSingle(IFormatProvider provider)
float IConvertible.ToSingle(IFormatProvider provider)
{
throw new InvalidCastException();
}

public string ToString(IFormatProvider provider)
string IConvertible.ToString(IFormatProvider provider)
{
throw new InvalidCastException();
}

public ushort ToUInt16(IFormatProvider provider)
ushort IConvertible.ToUInt16(IFormatProvider provider)
{
throw new InvalidCastException();
}

public uint ToUInt32(IFormatProvider provider)
uint IConvertible.ToUInt32(IFormatProvider provider)
{
throw new InvalidCastException();
}

public ulong ToUInt64(IFormatProvider provider)
ulong IConvertible.ToUInt64(IFormatProvider provider)
{
throw new InvalidCastException();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1391,6 +1391,12 @@ public void correctlyDeserializesStructUnions() {
assertTrue(StructUnionConsumer.isStructB(b1));
}

@Test
public void testStructsCanBeDowncastedToParentType() {
assertNotNull(Demonstrate982.takeThis());
assertNotNull(Demonstrate982.takeThisToo());
}

static class PartiallyInitializedThisConsumerImpl extends PartiallyInitializedThisConsumer {
@Override
public String consumePartiallyInitializedThis(final ConstructorPassesThisOut obj,
Expand Down

0 comments on commit c977e34

Please sign in to comment.