Skip to content

Commit 828a26f

Browse files
costleyaElad Ben-Israel
authored and
Elad Ben-Israel
committed
fix(dotnet): abstract classes should have proxy implementations (#241)
Abstract classes have can implementations that are not shared thorugh jsii. When trying to instantiate one for these types as a return value, the runtime crashes. This change creates proxy class types for abstract classes. These types can be instantiated, and have implementations for any methods/properties marked as abstract. Fixes #223 Add the ReturnsAbstract test to the complaince suite. This test ensures that proxy implementations are returned from methods that return a abstract class or interface. * Add .DS_Store to .gitignore * Rename JsiiInterfaceProxyAttribute to JsiiTypeProxy * Proxy implementations are sealed * Removed redundant checks for proxy resolution in runtime.
1 parent 04cb6bb commit 828a26f

File tree

53 files changed

+1022
-287
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+1022
-287
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -368,3 +368,6 @@ jspm_packages/
368368
# that NuGet uses for external packages. We don't want to ignore the
369369
# lerna packages.
370370
!/packages/*
371+
372+
#MacOS metadata
373+
.DS_Store
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
using Amazon.JSII.Generator.Class;
2+
using Amazon.JSII.JsonModel.Spec;
3+
using Xunit;
4+
5+
namespace Amazon.JSII.Generator.UnitTests.Class
6+
{
7+
public class AbstractClassProxyGeneratorTests : GeneratorTestBase
8+
{
9+
private const string Prefix = nameof(Generator) + "." + nameof(AbstractClassProxyGenerator) + ".";
10+
11+
private string Render(ClassType classType)
12+
{
13+
Symbols.MapTypeToPackage("myFqn", classType.Assembly);
14+
Symbols.MapNamespace(classType.QualifiedNamespace, "MyNamespace");
15+
Symbols.MapTypeName("myFqn", "MyClass", TypeKind.Class);
16+
17+
var generator = new AbstractClassProxyGenerator(classType.Assembly, classType, Symbols, Namespaces);
18+
19+
var syntaxTree = generator.CreateSyntaxTree();
20+
return syntaxTree.ToString();
21+
}
22+
23+
[Fact(DisplayName = Prefix + nameof(IncludesAttribute))]
24+
public void IncludesAttribute()
25+
{
26+
var classType = new ClassType
27+
(
28+
"myFqn",
29+
"myPackage",
30+
"myClass",
31+
true,
32+
initializer: new Method(true, false, false)
33+
);
34+
35+
var actual = Render(classType);
36+
var expected =
37+
@"namespace MyNamespace
38+
{
39+
[JsiiTypeProxy(typeof(MyClass), ""myFqn"")]
40+
internal sealed class MyClassProxy : MyClass
41+
{
42+
private MyClassProxy(ByRefValue reference): base(reference)
43+
{
44+
}
45+
}
46+
}";
47+
Assert.Equal(expected, actual, ignoreLineEndingDifferences: true);
48+
}
49+
50+
[Fact(DisplayName = Prefix + nameof(IncludesDocs))]
51+
public void IncludesDocs()
52+
{
53+
// Docs are not currently generated as part of the C# code.
54+
ClassType classType = new ClassType
55+
(
56+
fullyQualifiedName: "myFqn",
57+
assembly: "myPackage",
58+
name: "myClass",
59+
isAbstract: true,
60+
initializer: new Method(true, false, false),
61+
docs: new Docs {{"foo", "bar"}}
62+
);
63+
64+
string actual = Render(classType);
65+
string expected =
66+
@"namespace MyNamespace
67+
{
68+
/// <remarks>foo: bar</remarks>
69+
[JsiiTypeProxy(typeof(MyClass), ""myFqn"")]
70+
internal sealed class MyClassProxy : MyClass
71+
{
72+
private MyClassProxy(ByRefValue reference): base(reference)
73+
{
74+
}
75+
}
76+
}";
77+
Assert.Equal(expected, actual, ignoreLineEndingDifferences: true);
78+
}
79+
80+
[Fact(DisplayName = Prefix + nameof(IncludesProperties))]
81+
public void IncludesProperties()
82+
{
83+
ClassType classType = new ClassType
84+
(
85+
fullyQualifiedName: "myFqn",
86+
assembly: "myPackage",
87+
name: "myClass",
88+
isAbstract: true,
89+
initializer: new Method(true, false, false),
90+
properties: new[]
91+
{
92+
new Property
93+
(
94+
name: "myProp",
95+
type: new TypeReference("myPropTypeFqn"),
96+
isImmutable: false,
97+
isAbstract: true,
98+
isProtected: false
99+
),
100+
new Property
101+
(
102+
name: "notMyProp",
103+
type: new TypeReference("myPropTypeFqn"),
104+
isImmutable: false,
105+
isAbstract: false,
106+
isProtected: false
107+
)
108+
}
109+
);
110+
111+
Symbols.MapTypeName("myPropTypeFqn", "MyPropType", TypeKind.Class);
112+
Symbols.MapPropertyName("myFqn", "myProp", "MyProp");
113+
114+
string actual = Render(classType);
115+
string expected =
116+
@"namespace MyNamespace
117+
{
118+
[JsiiTypeProxy(typeof(MyClass), ""myFqn"")]
119+
internal sealed class MyClassProxy : MyClass
120+
{
121+
private MyClassProxy(ByRefValue reference): base(reference)
122+
{
123+
}
124+
125+
[JsiiProperty(""myProp"", ""{\""fqn\"":\""myPropTypeFqn\""}"")]
126+
public override MyPropType MyProp
127+
{
128+
get => GetInstanceProperty<MyPropType>();
129+
set => SetInstanceProperty(value);
130+
}
131+
}
132+
}";
133+
Assert.Equal(expected, actual, ignoreLineEndingDifferences: true);
134+
}
135+
136+
[Fact(DisplayName = Prefix + nameof(IncludesMethods))]
137+
public void IncludesMethods()
138+
{
139+
ClassType classType = new ClassType
140+
(
141+
fullyQualifiedName: "myFqn",
142+
assembly: "myPackage",
143+
name: "myClass",
144+
isAbstract: true,
145+
initializer: new Method(true, false, false),
146+
methods: new[]
147+
{
148+
new Method
149+
(
150+
false,
151+
false,
152+
true,
153+
name: "myMethod"
154+
),
155+
new Method
156+
(
157+
false,
158+
false,
159+
false,
160+
name: "notMyMethod"
161+
)
162+
}
163+
);
164+
165+
Symbols.MapMethodName("myFqn", "myMethod", "MyMethod");
166+
167+
string actual = Render(classType);
168+
string expected =
169+
@"namespace MyNamespace
170+
{
171+
[JsiiTypeProxy(typeof(MyClass), ""myFqn"")]
172+
internal sealed class MyClassProxy : MyClass
173+
{
174+
private MyClassProxy(ByRefValue reference): base(reference)
175+
{
176+
}
177+
178+
[JsiiMethod(""myMethod"", null, ""[]"")]
179+
public override void MyMethod()
180+
{
181+
InvokeInstanceVoidMethod(new object[]{});
182+
}
183+
}
184+
}";
185+
Assert.Equal(expected, actual, ignoreLineEndingDifferences: true);
186+
}
187+
}
188+
}

packages/jsii-dotnet-generator/src/Amazon.JSII.Generator.UnitTests/Class/ClassGeneratorTests.cs

+18-18
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
using Amazon.JSII.Generator.Class;
2-
using Amazon.JSII.Generator.Interface;
32
using Amazon.JSII.JsonModel.Spec;
43
using Microsoft.CodeAnalysis;
54
using Xunit;
5+
using TypeKind = Amazon.JSII.JsonModel.Spec.TypeKind;
66

77
namespace Amazon.JSII.Generator.UnitTests.Class
88
{
99
public class ClassGeneratorTests : GeneratorTestBase
1010
{
11-
const string Prefix = nameof(Generator) + "." + nameof(InterfaceGenerator) + ".";
11+
const string Prefix = nameof(Generator) + "." + nameof(ClassGenerator) + ".";
1212

13-
string Render(ClassType classType)
13+
private string Render(ClassType classType)
1414
{
1515
Symbols.MapTypeToPackage("myFqn", classType.Assembly);
1616
Symbols.MapNamespace(classType.QualifiedNamespace, "MyNamespace");
17-
Symbols.MapTypeName("myFqn", "MyClass", JsonModel.Spec.TypeKind.Class);
17+
Symbols.MapTypeName("myFqn", "MyClass", TypeKind.Class);
1818

1919
ClassGenerator generator = new ClassGenerator(classType.Assembly, classType, Symbols, Namespaces);
2020

@@ -36,7 +36,7 @@ public void IncludesAttribute()
3636

3737
string actual = Render(classType);
3838
string expected =
39-
@"namespace MyNamespace
39+
@"namespace MyNamespace
4040
{
4141
[JsiiClass(typeof(MyClass), ""myFqn"", ""[]"")]
4242
public class MyClass : DeputyBase
@@ -71,7 +71,7 @@ public void IncludesAbstractKeyword()
7171

7272
string actual = Render(classType);
7373
string expected =
74-
@"namespace MyNamespace
74+
@"namespace MyNamespace
7575
{
7676
[JsiiClass(typeof(MyClass), ""myFqn"", ""[]"")]
7777
public abstract class MyClass : DeputyBase
@@ -103,12 +103,12 @@ public void IncludesDocs()
103103
name: "myClass",
104104
isAbstract: false,
105105
initializer: new Method(true, false, false),
106-
docs: new Docs { { "foo", "bar" } }
106+
docs: new Docs {{"foo", "bar"}}
107107
);
108108

109109
string actual = Render(classType);
110110
string expected =
111-
@"namespace MyNamespace
111+
@"namespace MyNamespace
112112
{
113113
/// <remarks>foo: bar</remarks>
114114
[JsiiClass(typeof(MyClass), ""myFqn"", ""[]"")]
@@ -144,7 +144,7 @@ public void IncludesProperties()
144144
{
145145
new Property
146146
(
147-
name: "myProp",
147+
name: "myProp",
148148
type: new TypeReference("myPropTypeFqn"),
149149
isImmutable: false,
150150
isAbstract: false,
@@ -153,12 +153,12 @@ public void IncludesProperties()
153153
}
154154
);
155155

156-
Symbols.MapTypeName("myPropTypeFqn", "MyPropType", JsonModel.Spec.TypeKind.Class);
156+
Symbols.MapTypeName("myPropTypeFqn", "MyPropType", TypeKind.Class);
157157
Symbols.MapPropertyName("myFqn", "myProp", "MyProp");
158158

159159
string actual = Render(classType);
160160
string expected =
161-
@"namespace MyNamespace
161+
@"namespace MyNamespace
162162
{
163163
[JsiiClass(typeof(MyClass), ""myFqn"", ""[]"")]
164164
public class MyClass : DeputyBase
@@ -212,7 +212,7 @@ public void IncludesMethods()
212212

213213
string actual = Render(classType);
214214
string expected =
215-
@"namespace MyNamespace
215+
@"namespace MyNamespace
216216
{
217217
[JsiiClass(typeof(MyClass), ""myFqn"", ""[]"")]
218218
public class MyClass : DeputyBase
@@ -252,11 +252,11 @@ public void IncludesBase()
252252
@base: new TypeReference("myBaseTypeFqn")
253253
);
254254

255-
Symbols.MapTypeName("myBaseTypeFqn", "MyBaseType", JsonModel.Spec.TypeKind.Class);
255+
Symbols.MapTypeName("myBaseTypeFqn", "MyBaseType", TypeKind.Class);
256256

257257
string actual = Render(classType);
258258
string expected =
259-
@"namespace MyNamespace
259+
@"namespace MyNamespace
260260
{
261261
[JsiiClass(typeof(MyClass), ""myFqn"", ""[]"")]
262262
public class MyClass : MyBaseType
@@ -294,12 +294,12 @@ public void IncludesInterfaces()
294294
}
295295
);
296296

297-
Symbols.MapTypeName("myInterfaceFqn1", "MyInterface1", JsonModel.Spec.TypeKind.Interface);
298-
Symbols.MapTypeName("myInterfaceFqn2", "MyInterface2", JsonModel.Spec.TypeKind.Interface);
297+
Symbols.MapTypeName("myInterfaceFqn1", "MyInterface1", TypeKind.Interface);
298+
Symbols.MapTypeName("myInterfaceFqn2", "MyInterface2", TypeKind.Interface);
299299

300300
string actual = Render(classType);
301301
string expected =
302-
@"namespace MyNamespace
302+
@"namespace MyNamespace
303303
{
304304
[JsiiClass(typeof(MyClass), ""myFqn"", ""[]"")]
305305
public class MyClass : DeputyBase, IMyInterface1, IMyInterface2
@@ -320,4 +320,4 @@ protected MyClass(DeputyProps props): base(props)
320320
Assert.Equal(expected, actual, ignoreLineEndingDifferences: true);
321321
}
322322
}
323-
}
323+
}

0 commit comments

Comments
 (0)