Skip to content

Commit 5c8214b

Browse files
committed
Incomplete support for C# 14 Extension Members
This commit at least generates compilable code but it doesn't yet tie extension members to their extended type.
1 parent 38b310f commit 5c8214b

File tree

14 files changed

+1145
-71
lines changed

14 files changed

+1145
-71
lines changed

Generator/Beyond.NET.CodeGenerator/Collectors/MemberCollector.cs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,12 @@ out Dictionary<MemberInfo, string> unsupportedMembers
7979
continue;
8080
}
8181

82+
if (memberInfo.GetCustomAttribute<System.Runtime.CompilerServices.ExtensionMarkerAttribute>() is not null) {
83+
unsupportedMembers[memberInfo] = "Has extension marker";
84+
85+
continue;
86+
}
87+
8288
if (isInterface)
8389
{
8490
if (memberInfo is MethodInfo { IsAbstract: true, IsStatic: true })
@@ -157,6 +163,24 @@ Dictionary<MemberInfo, string> unsupportedMembers
157163
CollectEvent((EventInfo)memberInfo, collectedMembers, unsupportedMembers);
158164

159165
break;
166+
// case MemberTypes.NestedType:
167+
// // TODO: This might be useful to retrieve a mapping from a C# 14 extension type to the extended type
168+
// if (memberInfo.GetCustomAttribute<System.Runtime.CompilerServices.ExtensionAttribute>() is not null &&
169+
// memberInfo is TypeInfo typeInfo) {
170+
// var nestedType = typeInfo.DeclaredNestedTypes.FirstOrDefault();
171+
//
172+
// if (nestedType is not null &&
173+
// nestedType.GetMember("<Extension>$").FirstOrDefault() is MethodInfo extensionMember) {
174+
// var extensionType = typeInfo.AsType();
175+
// var extensionTargetType = extensionMember.GetParameters().FirstOrDefault()?.ParameterType;
176+
//
177+
// // TODO: Store this mapping somewhere so that we can refer to it later on
178+
//
179+
// Console.WriteLine("TODO");
180+
// }
181+
// }
182+
//
183+
// break;
160184
default:
161185
unsupportedMembers[memberInfo] = $"Unsupported member type: {memberType}";
162186

@@ -351,4 +375,4 @@ out string? unsupportedReason
351375

352376
return true;
353377
}
354-
}
378+
}

Generator/Beyond.NET.CodeGenerator/Collectors/TypeCollector.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,11 @@ out string? unsupportedReason
395395
return false;
396396
}
397397

398+
if (type.IsSpecialName) {
399+
unsupportedReason = "Has special name (maybe C# 14 extension type?)";
400+
return false;
401+
}
402+
398403
bool isNullableValueType = type.IsNullableValueType(out Type? nullableValueType);
399404

400405
// Only nullable structs, not primitives or enums are currently supported

Generator/Beyond.NET.CodeGenerator/Extensions/TypeExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,4 +318,4 @@ Type interfaceType
318318

319319
return false;
320320
}
321-
}
321+
}

Generator/Beyond.NET.CodeGenerator/Syntax/C/CTypeSyntaxWriter.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ public string WriteMembers(Type type, State state, ISyntaxWriterConfiguration? c
307307
string fullTypeName = type.GetFullNameOrName();
308308

309309
bool isDelegate = type.IsDelegate();
310-
310+
311311
sb.AppendLine(
312312
Builder.PragmaMark()
313313
.Separator()
@@ -592,4 +592,4 @@ out ICSyntaxWriter? syntaxWriter
592592

593593
return syntaxWriter;
594594
}
595-
}
595+
}

Generator/Beyond.NET.CodeGenerator/Syntax/GeneratedMember.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,9 @@ out string? generatedName
4040

4141
return generatedName;
4242
}
43-
}
43+
44+
public override string ToString()
45+
{
46+
return Member?.ToString() ?? MemberKind.ToString();
47+
}
48+
}

Generator/Beyond.NET.CodeGenerator/Syntax/State.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ public IEnumerable<GeneratedMember> GetGeneratedMembersThatAreExtensions()
136136
continue;
137137
}
138138

139+
// TODO: This doesn't work for C# 14 extensions as they don't have the System.Runtime.CompilerServices.ExtensionAttribute
140+
// Instead, they seem to emit some additional nested type which we maybe could use to retrieve the mapping between extension type and extended type. There's also a new attribute: System.Runtime.CompilerServices.ExtensionMarkerAttribute
139141
if (declaringType.IsGenericType ||
140142
declaringType.IsNested ||
141143
!declaringType.IsSealed ||
@@ -154,4 +156,4 @@ public IEnumerable<GeneratedMember> GetGeneratedMembersThatAreExtensions()
154156

155157
return extensionMembers;
156158
}
157-
}
159+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace Beyond.NET.Sample.Source;
2+
3+
public class NestedTypeTests
4+
{
5+
public class MyNestedType
6+
{
7+
public static bool PrettyLonelyAroundHere => true;
8+
}
9+
}

Samples/Beyond.NET.Sample.Managed/Source/Person.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ string lastName
115115
/// <summary>
116116
/// Creates a Person named "John Doe" who is 50 years old.
117117
/// </summary>
118-
/// <returns>Your new John New</returns>
118+
/// <returns>Your new John Doe</returns>
119119
public static Person MakeJohnDoe() => new("John", "Doe", 50);
120120

121121
/// <summary>

Samples/Beyond.NET.Sample.Managed/Source/Person_Extensions.cs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,38 @@ out Address? address
1919

2020
return address is not null;
2121
}
22-
}
22+
23+
extension(Person person)
24+
{
25+
/// <summary>
26+
/// Creates a Person named "Jane Doe" who is 50 years old.
27+
/// Implemented as a .NET 10 static extension method.
28+
/// </summary>
29+
/// <returns>Your new Jane Doe</returns>
30+
public static Person MakeJaneDoe() => new("Jane", "Doe", 50);
31+
32+
33+
/// <summary>
34+
/// Increases the target person's age by 1.
35+
/// Implemented as a .NET 10 extension method.
36+
/// </summary>
37+
public void CelerbrateBirthday()
38+
{
39+
person.IncreaseAge(1);
40+
}
41+
42+
/// <summary>
43+
/// The getter returns `true` if the target person's nice level is `NotNice` or "lower". When `true` is passed to the setter, it sets the person's nice level to `NotNice`, when `false` it doesn't do anything.
44+
/// Implemented as a .NET 10 extension property.
45+
/// </summary>
46+
public bool IsMean
47+
{
48+
get => person.NiceLevel <= NiceLevels.NotNice;
49+
set {
50+
if (value) {
51+
person.NiceLevel = NiceLevels.NotNice;
52+
}
53+
}
54+
}
55+
}
56+
}

Samples/Beyond.NET.Sample.Swift/Tests/TestClasses/PersonTests.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,4 +334,14 @@ final class PersonTests: XCTestCase {
334334

335335
XCTAssertEqual(2, numberOfChildrenAfterDaugther)
336336
}
337+
338+
func testPersonExtensions() throws {
339+
// TODO: Support for C# 14 extension methods is incomplete!
340+
341+
let janeDoe = try Beyond_NET_Sample_Person_Extensions.makeJaneDoe()
342+
try XCTAssertEqual(janeDoe.fullName.string(), "Jane Doe")
343+
344+
// let janeDoe = try Beyond_NET_Sample_Person.makeJaneDoe()
345+
// try XCTAssertEqual(janeDoe.fullName.string(), "Jane Doe")
346+
}
337347
}

0 commit comments

Comments
 (0)