Skip to content

Commit 8a390e5

Browse files
authored
fix(dotnet): allow down-casting to parent interface type (#983)
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
1 parent c2de100 commit 8a390e5

22 files changed

Lines changed: 1004 additions & 23 deletions

File tree

packages/jsii-calc/lib/compliance.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2236,3 +2236,33 @@ export class SomeTypeJsii976 {
22362236
return new Derived();
22372237
}
22382238
}
2239+
2240+
/** https://github.com/aws/jsii/issues/982 */
2241+
export interface ParentStruct982 {
2242+
readonly foo: string;
2243+
}
2244+
export interface ChildStruct982 extends ParentStruct982 {
2245+
readonly bar: number;
2246+
}
2247+
/**
2248+
* 1. call #takeThis() -> An ObjectRef will be provisioned for the value (it'll be re-used!)
2249+
* 2. call #takeThisToo() -> The ObjectRef from before will need to be down-cased to the ParentStruct982 type
2250+
*/
2251+
export class Demonstrate982 {
2252+
private static readonly value = {
2253+
foo: 'foo',
2254+
bar: 1337,
2255+
};
2256+
2257+
/** It's dangerous to go alone! */
2258+
public static takeThis(): ChildStruct982 {
2259+
return this.value;
2260+
}
2261+
2262+
/** It's dangerous to go alone! */
2263+
public static takeThisToo(): ParentStruct982 {
2264+
return this.value;
2265+
}
2266+
2267+
public constructor() { }
2268+
}

packages/jsii-calc/test/assembly.jsii

Lines changed: 123 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1678,6 +1678,40 @@
16781678
}
16791679
]
16801680
},
1681+
"jsii-calc.ChildStruct982": {
1682+
"assembly": "jsii-calc",
1683+
"datatype": true,
1684+
"docs": {
1685+
"stability": "experimental"
1686+
},
1687+
"fqn": "jsii-calc.ChildStruct982",
1688+
"interfaces": [
1689+
"jsii-calc.ParentStruct982"
1690+
],
1691+
"kind": "interface",
1692+
"locationInModule": {
1693+
"filename": "lib/compliance.ts",
1694+
"line": 2244
1695+
},
1696+
"name": "ChildStruct982",
1697+
"properties": [
1698+
{
1699+
"abstract": true,
1700+
"docs": {
1701+
"stability": "experimental"
1702+
},
1703+
"immutable": true,
1704+
"locationInModule": {
1705+
"filename": "lib/compliance.ts",
1706+
"line": 2245
1707+
},
1708+
"name": "bar",
1709+
"type": {
1710+
"primitive": "number"
1711+
}
1712+
}
1713+
]
1714+
},
16811715
"jsii-calc.ClassThatImplementsTheInternalInterface": {
16821716
"assembly": "jsii-calc",
16831717
"docs": {
@@ -2825,6 +2859,62 @@
28252859
}
28262860
]
28272861
},
2862+
"jsii-calc.Demonstrate982": {
2863+
"assembly": "jsii-calc",
2864+
"docs": {
2865+
"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",
2866+
"stability": "experimental",
2867+
"summary": "1."
2868+
},
2869+
"fqn": "jsii-calc.Demonstrate982",
2870+
"initializer": {
2871+
"docs": {
2872+
"stability": "experimental"
2873+
}
2874+
},
2875+
"kind": "class",
2876+
"locationInModule": {
2877+
"filename": "lib/compliance.ts",
2878+
"line": 2251
2879+
},
2880+
"methods": [
2881+
{
2882+
"docs": {
2883+
"stability": "experimental",
2884+
"summary": "It's dangerous to go alone!"
2885+
},
2886+
"locationInModule": {
2887+
"filename": "lib/compliance.ts",
2888+
"line": 2258
2889+
},
2890+
"name": "takeThis",
2891+
"returns": {
2892+
"type": {
2893+
"fqn": "jsii-calc.ChildStruct982"
2894+
}
2895+
},
2896+
"static": true
2897+
},
2898+
{
2899+
"docs": {
2900+
"stability": "experimental",
2901+
"summary": "It's dangerous to go alone!"
2902+
},
2903+
"locationInModule": {
2904+
"filename": "lib/compliance.ts",
2905+
"line": 2263
2906+
},
2907+
"name": "takeThisToo",
2908+
"returns": {
2909+
"type": {
2910+
"fqn": "jsii-calc.ParentStruct982"
2911+
}
2912+
},
2913+
"static": true
2914+
}
2915+
],
2916+
"name": "Demonstrate982"
2917+
},
28282918
"jsii-calc.DeprecatedClass": {
28292919
"assembly": "jsii-calc",
28302920
"docs": {
@@ -7649,6 +7739,38 @@
76497739
],
76507740
"name": "OverrideReturnsObject"
76517741
},
7742+
"jsii-calc.ParentStruct982": {
7743+
"assembly": "jsii-calc",
7744+
"datatype": true,
7745+
"docs": {
7746+
"stability": "experimental",
7747+
"summary": "https://github.com/aws/jsii/issues/982."
7748+
},
7749+
"fqn": "jsii-calc.ParentStruct982",
7750+
"kind": "interface",
7751+
"locationInModule": {
7752+
"filename": "lib/compliance.ts",
7753+
"line": 2241
7754+
},
7755+
"name": "ParentStruct982",
7756+
"properties": [
7757+
{
7758+
"abstract": true,
7759+
"docs": {
7760+
"stability": "experimental"
7761+
},
7762+
"immutable": true,
7763+
"locationInModule": {
7764+
"filename": "lib/compliance.ts",
7765+
"line": 2242
7766+
},
7767+
"name": "foo",
7768+
"type": {
7769+
"primitive": "string"
7770+
}
7771+
}
7772+
]
7773+
},
76527774
"jsii-calc.PartiallyInitializedThisConsumer": {
76537775
"abstract": true,
76547776
"assembly": "jsii-calc",
@@ -11006,5 +11128,5 @@
1100611128
}
1100711129
},
1100811130
"version": "0.20.3",
11009-
"fingerprint": "umMeNAH41pX11GUAjHkw6RTyAwGCeTqRNJ2vPv5aeM0="
11131+
"fingerprint": "cb2xJuwu7eu9+ulwYnbZUyzYyE3IkL5fCM4b8Gqr7Vo="
1101011132
}

packages/jsii-dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests/Amazon.JSII.Runtime.IntegrationTests.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,8 @@
1919
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />
2020
</ItemGroup>
2121

22+
<ItemGroup>
23+
<ProjectReference Include="..\..\..\jsii-dotnet-runtime\src\Amazon.JSII.Runtime\Amazon.JSII.Runtime.csproj" />
24+
</ItemGroup>
25+
2226
</Project>

packages/jsii-dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests/ComplianceTests.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1258,5 +1258,12 @@ public double Next()
12581258
return next;
12591259
}
12601260
}
1261+
1262+
[Fact(DisplayName = Prefix + nameof(StructsCanBeDowncastedToParentType))]
1263+
public void StructsCanBeDowncastedToParentType()
1264+
{
1265+
Assert.NotNull(Demonstrate982.TakeThis());
1266+
Assert.NotNull(Demonstrate982.TakeThisToo());
1267+
}
12611268
}
12621269
}

packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime/Deputy/DeputyBase.cs

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -455,12 +455,12 @@ private static JsiiMethodAttribute GetMethodAttributeCore(System.Type type, stri
455455

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

458-
public TypeCode GetTypeCode()
458+
TypeCode IConvertible.GetTypeCode()
459459
{
460460
return TypeCode.Object;
461461
}
462462

463-
public object ToType(System.Type conversionType, IFormatProvider provider)
463+
object IConvertible.ToType(System.Type conversionType, IFormatProvider provider)
464464
{
465465
if (Proxies.ContainsKey(conversionType))
466466
{
@@ -498,14 +498,15 @@ bool ToTypeCore(out object result)
498498
return false;
499499
}
500500

501-
if (!Reference.Interfaces.Contains(interfaceAttribute.FullyQualifiedName))
501+
var types = ServiceContainer.ServiceProvider.GetRequiredService<ITypeCache>();
502+
503+
if (!TryFindSupportedInterface(interfaceAttribute.FullyQualifiedName, Reference.Interfaces, types, out var adequateFqn))
502504
{
503505
// We can only convert to interfaces declared by this Reference
504506
result = null;
505507
return false;
506508
}
507-
508-
var types = ServiceContainer.ServiceProvider.GetRequiredService<ITypeCache>();
509+
509510
var proxyType = types.GetProxyType(interfaceAttribute.FullyQualifiedName);
510511
var constructorInfo = proxyType.GetConstructor(
511512
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
@@ -520,82 +521,100 @@ bool ToTypeCore(out object result)
520521

521522
result = constructorInfo.Invoke(new object[]{ Reference.ForProxy() });
522523
return true;
524+
525+
bool TryFindSupportedInterface(string declaredFqn, string[] availableFqns, ITypeCache types, out string foundFqn)
526+
{
527+
var declaredType = types.GetInterfaceType(declaredFqn);
528+
529+
foreach (var candidate in availableFqns)
530+
{
531+
var candidateType = types.GetInterfaceType(candidate);
532+
if (declaredType.IsAssignableFrom(candidateType))
533+
{
534+
foundFqn = candidate;
535+
return true;
536+
}
537+
}
538+
539+
foundFqn = null;
540+
return false;
541+
}
523542
}
524543
}
525544

526545
#region Impossible Conversions
527546

528-
public bool ToBoolean(IFormatProvider provider)
547+
bool IConvertible.ToBoolean(IFormatProvider provider)
529548
{
530549
throw new InvalidCastException();
531550
}
532551

533-
public byte ToByte(IFormatProvider provider)
552+
byte IConvertible.ToByte(IFormatProvider provider)
534553
{
535554
throw new InvalidCastException();
536555
}
537556

538-
public char ToChar(IFormatProvider provider)
557+
char IConvertible.ToChar(IFormatProvider provider)
539558
{
540559
throw new InvalidCastException();
541560
}
542561

543-
public DateTime ToDateTime(IFormatProvider provider)
562+
DateTime IConvertible.ToDateTime(IFormatProvider provider)
544563
{
545564
throw new InvalidCastException();
546565
}
547566

548-
public decimal ToDecimal(IFormatProvider provider)
567+
decimal IConvertible.ToDecimal(IFormatProvider provider)
549568
{
550569
throw new InvalidCastException();
551570
}
552571

553-
public double ToDouble(IFormatProvider provider)
572+
double IConvertible.ToDouble(IFormatProvider provider)
554573
{
555574
throw new InvalidCastException();
556575
}
557576

558-
public short ToInt16(IFormatProvider provider)
577+
short IConvertible.ToInt16(IFormatProvider provider)
559578
{
560579
throw new InvalidCastException();
561580
}
562581

563-
public int ToInt32(IFormatProvider provider)
582+
int IConvertible.ToInt32(IFormatProvider provider)
564583
{
565584
throw new InvalidCastException();
566585
}
567586

568-
public long ToInt64(IFormatProvider provider)
587+
long IConvertible.ToInt64(IFormatProvider provider)
569588
{
570589
throw new InvalidCastException();
571590
}
572591

573-
public sbyte ToSByte(IFormatProvider provider)
592+
sbyte IConvertible.ToSByte(IFormatProvider provider)
574593
{
575594
throw new InvalidCastException();
576595
}
577596

578-
public float ToSingle(IFormatProvider provider)
597+
float IConvertible.ToSingle(IFormatProvider provider)
579598
{
580599
throw new InvalidCastException();
581600
}
582601

583-
public string ToString(IFormatProvider provider)
602+
string IConvertible.ToString(IFormatProvider provider)
584603
{
585604
throw new InvalidCastException();
586605
}
587606

588-
public ushort ToUInt16(IFormatProvider provider)
607+
ushort IConvertible.ToUInt16(IFormatProvider provider)
589608
{
590609
throw new InvalidCastException();
591610
}
592611

593-
public uint ToUInt32(IFormatProvider provider)
612+
uint IConvertible.ToUInt32(IFormatProvider provider)
594613
{
595614
throw new InvalidCastException();
596615
}
597616

598-
public ulong ToUInt64(IFormatProvider provider)
617+
ulong IConvertible.ToUInt64(IFormatProvider provider)
599618
{
600619
throw new InvalidCastException();
601620
}

packages/jsii-java-runtime-test/project/src/test/java/software/amazon/jsii/testing/ComplianceTest.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1397,6 +1397,12 @@ public void returnSubclassThatImplementsInterface976() {
13971397
assertEquals(obj.getFoo(), 333);
13981398
}
13991399

1400+
@Test
1401+
public void testStructsCanBeDowncastedToParentType() {
1402+
assertNotNull(Demonstrate982.takeThis());
1403+
assertNotNull(Demonstrate982.takeThisToo());
1404+
}
1405+
14001406
static class PartiallyInitializedThisConsumerImpl extends PartiallyInitializedThisConsumer {
14011407
@Override
14021408
public String consumePartiallyInitializedThis(final ConstructorPassesThisOut obj,

0 commit comments

Comments
 (0)