Skip to content

Commit 127f2a3

Browse files
Merge pull request #352 from tannergooding/main
Improve UTF-8 string literal, Guid, and attribute handling
2 parents 624eed1 + bccf6af commit 127f2a3

File tree

9 files changed

+235
-29
lines changed

9 files changed

+235
-29
lines changed

sources/ClangSharp.Interop/Extensions/CXCursor.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,16 @@ public string AttrKindSpelling
2727
{
2828
Debug.Assert(CX_AttrKind.CX_AttrKind_FirstDeclOrTypeAttr == CX_AttrKind.CX_AttrKind_AArch64VectorPcs);
2929
Debug.Assert(CX_AttrKind.CX_AttrKind_LastDeclOrTypeAttr == CX_AttrKind.CX_AttrKind_VectorCall);
30-
Debug.Assert(CX_AttrKind.CX_AttrKind_FirstInheritableParamAttr == CX_AttrKind.CX_AttrKind_SwiftContext);
30+
Debug.Assert(CX_AttrKind.CX_AttrKind_FirstInheritableParamAttr == CX_AttrKind.CX_AttrKind_SwiftAsyncContext);
3131
Debug.Assert(CX_AttrKind.CX_AttrKind_LastInheritableParamAttr == CX_AttrKind.CX_AttrKind_UseHandle);
32-
Debug.Assert(CX_AttrKind.CX_AttrKind_FirstParameterABIAttr == CX_AttrKind.CX_AttrKind_SwiftContext);
32+
Debug.Assert(CX_AttrKind.CX_AttrKind_FirstParameterABIAttr == CX_AttrKind.CX_AttrKind_SwiftAsyncContext);
3333
Debug.Assert(CX_AttrKind.CX_AttrKind_LastParameterABIAttr == CX_AttrKind.CX_AttrKind_SwiftIndirectResult);
3434
Debug.Assert(CX_AttrKind.CX_AttrKind_LastAttr == CX_AttrKind.CX_AttrKind_Thread);
3535
Debug.Assert(CX_AttrKind.CX_AttrKind_FirstTypeAttr == CX_AttrKind.CX_AttrKind_AddressSpace);
3636
Debug.Assert(CX_AttrKind.CX_AttrKind_LastTypeAttr == CX_AttrKind.CX_AttrKind_UPtr);
3737
Debug.Assert(CX_AttrKind.CX_AttrKind_FirstStmtAttr == CX_AttrKind.CX_AttrKind_FallThrough);
38-
Debug.Assert(CX_AttrKind.CX_AttrKind_LastStmtAttr == CX_AttrKind.CX_AttrKind_Suppress);
39-
Debug.Assert(CX_AttrKind.CX_AttrKind_FirstInheritableAttr == CX_AttrKind.CX_AttrKind_AArch64VectorPcs);
38+
Debug.Assert(CX_AttrKind.CX_AttrKind_LastStmtAttr == CX_AttrKind.CX_AttrKind_Unlikely);
39+
Debug.Assert(CX_AttrKind.CX_AttrKind_FirstInheritableAttr == CX_AttrKind.CX_AttrKind_NoMerge);
4040
Debug.Assert(CX_AttrKind.CX_AttrKind_LastInheritableAttr == CX_AttrKind.CX_AttrKind_XRayLogArgs);
4141

4242
return AttrKind switch

sources/ClangSharp.PInvokeGenerator/Abstractions/ValueKind.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@ internal enum ValueKind
99
Enumerator,
1010
Unmanaged,
1111
String,
12+
GuidMember,
1213
}
1314
}

sources/ClangSharp.PInvokeGenerator/CSharp/CSharpOutputBuilder.VisitDecl.cs

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,13 @@ public void BeginValue(in ValueDesc desc)
147147
Write(desc.TypeName);
148148
Write(' ');
149149
}
150+
else if (desc.Kind == ValueKind.GuidMember)
151+
{
152+
Write("static ");
153+
Write(desc.TypeName);
154+
Write(' ');
155+
isExpressionBody = true;
156+
}
150157

151158
Write(desc.EscapedName);
152159

@@ -215,6 +222,13 @@ public void EndValue(in ValueDesc desc)
215222
WriteLine(';');
216223
break;
217224
}
225+
226+
case ValueKind.GuidMember:
227+
{
228+
WriteLine(';');
229+
NeedsNewline = true;
230+
break;
231+
}
218232
}
219233
}
220234

@@ -755,11 +769,20 @@ public void BeginStruct(in StructDesc desc)
755769
Write("partial struct ");
756770
Write(desc.EscapedName);
757771

758-
if (desc.HasVtbl && _config.GenerateMarkerInterfaces)
772+
if (_config.GenerateMarkerInterfaces)
759773
{
760-
Write(" : ");
761-
Write(desc.EscapedName);
762-
Write(".Interface");
774+
if (desc.HasVtbl)
775+
{
776+
Write(" : ");
777+
Write(desc.EscapedName);
778+
Write(".Interface");
779+
}
780+
781+
if ((desc.Uuid is not null) && _config.GenerateGuidMember && _config.GeneratePreviewCode)
782+
{
783+
Write(desc.HasVtbl ? ", " : " : ");
784+
Write("INativeGuid");
785+
}
763786
}
764787

765788
WriteNewline();

sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -567,9 +567,9 @@ private void VisitFunctionDecl(FunctionDecl functionDecl)
567567
CustomAttrGeneratorData = (functionDecl, _outputBuilder, this),
568568
};
569569

570-
_ = _isTopLevelClassUnsafe.TryGetValue(className, out var isUnsafe);
570+
_ = _topLevelClassIsUnsafe.TryGetValue(className, out var isUnsafe);
571571
_outputBuilder.BeginFunctionOrDelegate(in desc, ref isUnsafe);
572-
_isTopLevelClassUnsafe[className] = isUnsafe;
572+
_topLevelClassIsUnsafe[className] = isUnsafe;
573573

574574
_outputBuilder.BeginFunctionInnerPrototype(in desc);
575575

@@ -1255,17 +1255,19 @@ private void VisitRecordDecl(RecordDecl recordDecl)
12551255
_testOutputBuilder.WriteBlockStart();
12561256
}
12571257

1258-
Guid? nullableUuid = null;
1258+
var nullableUuid = (Guid?)null;
1259+
var uuidName = "";
1260+
12591261
if (TryGetUuid(recordDecl, out var uuid))
12601262
{
12611263
nullableUuid = uuid;
1262-
var iidName = GetRemappedName($"IID_{nativeName}", recordDecl, tryRemapOperatorName: false, out var wasRemapped, skipUsing: true);
1264+
uuidName = GetRemappedName($"IID_{nativeName}", recordDecl, tryRemapOperatorName: false, out var wasRemapped, skipUsing: true);
12631265

1264-
_uuidsToGenerate.Add(iidName, uuid);
1266+
_uuidsToGenerate.Add(uuidName, uuid);
12651267

12661268
if (_testOutputBuilder != null)
12671269
{
1268-
var className = GetClass(iidName);
1270+
var className = GetClass(uuidName);
12691271

12701272
_testOutputBuilder.AddUsingDirective("System");
12711273
_testOutputBuilder.AddUsingDirective($"static {GetNamespace(className)}.{className}");
@@ -1297,7 +1299,7 @@ private void VisitRecordDecl(RecordDecl recordDecl)
12971299
_testOutputBuilder.Write("Is.EqualTo(");
12981300
}
12991301

1300-
_testOutputBuilder.Write(iidName);
1302+
_testOutputBuilder.Write(uuidName);
13011303

13021304
if (_config.GenerateTestsNUnit)
13031305
{
@@ -1312,6 +1314,8 @@ private void VisitRecordDecl(RecordDecl recordDecl)
13121314
}
13131315
}
13141316

1317+
var hasGuidMember = _config.GenerateGuidMember && !string.IsNullOrWhiteSpace(uuidName);
1318+
13151319
var layoutKind = recordDecl.IsUnion
13161320
? LayoutKind.Explicit
13171321
: LayoutKind.Sequential;
@@ -1355,7 +1359,7 @@ private void VisitRecordDecl(RecordDecl recordDecl)
13551359
var desc = new StructDesc {
13561360
AccessSpecifier = GetAccessSpecifier(recordDecl),
13571361
EscapedName = escapedName,
1358-
IsUnsafe = IsUnsafe(recordDecl),
1362+
IsUnsafe = IsUnsafe(recordDecl) || hasGuidMember,
13591363
HasVtbl = hasVtbl || hasBaseVtbl,
13601364
IsUnion = recordDecl.IsUnion,
13611365
Layout = new() {
@@ -1440,10 +1444,43 @@ private void VisitRecordDecl(RecordDecl recordDecl)
14401444

14411445
if (desc.IsUnsafe)
14421446
{
1443-
_isTopLevelClassUnsafe[name] = true;
1447+
_topLevelClassIsUnsafe[name] = true;
1448+
}
1449+
1450+
if (hasGuidMember)
1451+
{
1452+
_topLevelClassHasGuidMember[name] = true;
14441453
}
14451454
}
14461455

1456+
if (hasGuidMember)
1457+
{
1458+
var valueDesc = new ValueDesc {
1459+
AccessSpecifier = AccessSpecifier.None,
1460+
TypeName = "Guid*",
1461+
EscapedName = "INativeGuid.NativeGuid",
1462+
ParentName = name,
1463+
Kind = ValueKind.GuidMember,
1464+
Flags = ValueFlags.Initializer,
1465+
};
1466+
1467+
var uuidClassName = GetClass(uuidName);
1468+
1469+
_outputBuilder.EmitUsingDirective("System");
1470+
_outputBuilder.EmitUsingDirective("System.Runtime.CompilerServices");
1471+
1472+
_outputBuilder.EmitUsingDirective($"static {GetNamespace(uuidClassName)}.{uuidClassName}");
1473+
_outputBuilder.BeginValue(in valueDesc);
1474+
1475+
var code = _outputBuilder.BeginCSharpCode();
1476+
code.Write("(Guid*)Unsafe.AsPointer(ref Unsafe.AsRef(in ");
1477+
code.Write(uuidName);
1478+
code.Write("))");
1479+
_outputBuilder.EndCSharpCode(code);
1480+
1481+
_outputBuilder.EndValue(in valueDesc);
1482+
}
1483+
14471484
if (hasVtbl || (hasBaseVtbl && !HasBaseField(cxxRecordDecl)))
14481485
{
14491486
var fieldDesc = new FieldDesc {
@@ -3091,7 +3128,7 @@ private void VisitVarDecl(VarDecl varDecl)
30913128

30923129
if (IsUnsafe(varDecl, type) && (!varDecl.HasInit || !IsStmtAsWritten<StringLiteral>(varDecl.Init, out _, removeParens: true)))
30933130
{
3094-
_isTopLevelClassUnsafe[className] = true;
3131+
_topLevelClassIsUnsafe[className] = true;
30953132
}
30963133
}
30973134

sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitStmt.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2296,7 +2296,8 @@ private void VisitStringLiteral(StringLiteral stringLiteral)
22962296
{
22972297
outputBuilder.Write('"');
22982298
outputBuilder.Write(EscapeString(stringLiteral.String));
2299-
outputBuilder.Write("\\0\"u8");
2299+
outputBuilder.Write('"');
2300+
outputBuilder.Write("u8");
23002301
}
23012302
else
23022303
{
@@ -2380,7 +2381,7 @@ private void VisitUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr unaryExprOrT
23802381
{
23812382
if (_topLevelClassNames.Contains(_outputBuilder.Name))
23822383
{
2383-
_isTopLevelClassUnsafe[_outputBuilder.Name] = true;
2384+
_topLevelClassIsUnsafe[_outputBuilder.Name] = true;
23842385
}
23852386

23862387
var parentType = null as Type;

sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs

Lines changed: 75 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ public sealed partial class PInvokeGenerator : IDisposable
4141
private readonly Dictionary<string, HashSet<string>> _traversedValidNameRemappings;
4242
private readonly Dictionary<CXXMethodDecl, uint> _overloadIndices;
4343
private readonly Dictionary<Cursor, uint> _isExcluded;
44-
private readonly Dictionary<string, bool> _isTopLevelClassUnsafe;
44+
private readonly Dictionary<string, bool> _topLevelClassHasGuidMember;
45+
private readonly Dictionary<string, bool> _topLevelClassIsUnsafe;
4546
private readonly Dictionary<string, HashSet<string>> _topLevelClassUsings;
4647
private readonly Dictionary<string, List<string>> _topLevelClassAttributes;
4748
private readonly HashSet<string> _topLevelClassNames;
@@ -111,7 +112,8 @@ public PInvokeGenerator(PInvokeGeneratorConfiguration config, Func<string, Strea
111112
_traversedValidNameRemappings = new Dictionary<string, HashSet<string>>();
112113
_overloadIndices = new Dictionary<CXXMethodDecl, uint>();
113114
_isExcluded = new Dictionary<Cursor, uint>();
114-
_isTopLevelClassUnsafe = new Dictionary<string, bool>();
115+
_topLevelClassHasGuidMember = new Dictionary<string, bool>();
116+
_topLevelClassIsUnsafe = new Dictionary<string, bool>();
115117
_topLevelClassNames = new HashSet<string>();
116118
_topLevelClassAttributes = new Dictionary<string, List<string>>();
117119
_topLevelClassUsings = new Dictionary<string, HashSet<string>>();
@@ -280,6 +282,9 @@ public void Close()
280282
{
281283
foreach (var entry in methodClassOutputBuilders)
282284
{
285+
var hasGuidMember = _config.GenerateGuidMember && _config.GeneratePreviewCode;
286+
hasGuidMember &= _uuidsToGenerate.ContainsKey(entry.Value.Name) || _generatedUuids.Contains(entry.Value.Name);
287+
283288
CloseOutputBuilder(stream, entry.Value, isMethodClass: true, leaveStreamOpen, emitNamespaceDeclaration);
284289
}
285290

@@ -631,7 +636,7 @@ static void GenerateTransparentStructs(PInvokeGenerator generator)
631636

632637
sw.WriteLine();
633638

634-
sw.Write("namespace ");sw.WriteLine();
639+
sw.Write("namespace ");
635640
sw.Write(targetNamespace);
636641

637642
if (generator.Config.GenerateFileScopedNamespaces)
@@ -647,7 +652,7 @@ static void GenerateTransparentStructs(PInvokeGenerator generator)
647652
}
648653

649654
sw.Write(indentString);
650-
sw.Write("public ");
655+
sw.Write("public readonly ");
651656

652657
if (isTypePointer || IsTransparentStructHexBased(kind))
653658
{
@@ -1556,7 +1561,7 @@ void ForCSharp(CSharpOutputBuilder csharpOutputBuilder)
15561561
sw.Write("static ");
15571562
}
15581563

1559-
if ((_isTopLevelClassUnsafe.TryGetValue(nonTestName, out var isUnsafe) && isUnsafe) || (outputBuilder.IsTestOutput && isTopLevelStruct))
1564+
if ((_topLevelClassIsUnsafe.TryGetValue(nonTestName, out var isUnsafe) && isUnsafe) || (outputBuilder.IsTestOutput && isTopLevelStruct))
15601565
{
15611566
sw.Write("unsafe ");
15621567
}
@@ -1574,6 +1579,11 @@ void ForCSharp(CSharpOutputBuilder csharpOutputBuilder)
15741579

15751580
sw.Write(outputBuilder.Name);
15761581

1582+
if (_topLevelClassHasGuidMember.TryGetValue(outputBuilder.Name, out var hasGuidMember) && hasGuidMember)
1583+
{
1584+
sw.Write(" : INativeGuid");
1585+
}
1586+
15771587
sw.WriteLine();
15781588
sw.Write(indentationString);
15791589
sw.Write('{');
@@ -1635,7 +1645,7 @@ void ForXml(XmlOutputBuilder xmlOutputBuilder)
16351645
sw.Write(xmlOutputBuilder.Name);
16361646
sw.Write("\" access=\"public\" static=\"true\"");
16371647

1638-
if (_isTopLevelClassUnsafe.TryGetValue(xmlOutputBuilder.Name, out var isUnsafe) && isUnsafe)
1648+
if (_topLevelClassIsUnsafe.TryGetValue(xmlOutputBuilder.Name, out var isUnsafe) && isUnsafe)
16391649
{
16401650
sw.Write(" unsafe=\"true\"");
16411651
}
@@ -5388,6 +5398,12 @@ private void StopUsingOutputBuilder()
53885398

53895399
private bool TryGetUuid(RecordDecl recordDecl, out Guid uuid)
53905400
{
5401+
if (TryGetRemappedValue(recordDecl, _config.WithGuids, out var guid))
5402+
{
5403+
uuid = guid;
5404+
return true;
5405+
}
5406+
53915407
var uuidAttrs = recordDecl.Attrs.Where((attr) => attr.Kind == CX_AttrKind.CX_AttrKind_Uuid);
53925408

53935409
if (!uuidAttrs.Any())
@@ -5682,6 +5698,59 @@ private void WithAttributes(NamedDecl namedDecl, bool onlySupportedOSPlatform =
56825698
outputBuilder.WriteCustomAttribute(attribute);
56835699
}
56845700
}
5701+
5702+
if (!isTestOutput && namedDecl.HasAttrs)
5703+
{
5704+
foreach (var attr in namedDecl.Attrs)
5705+
{
5706+
switch (attr.Kind)
5707+
{
5708+
case CX_AttrKind.CX_AttrKind_Aligned:
5709+
case CX_AttrKind.CX_AttrKind_AlwaysInline:
5710+
case CX_AttrKind.CX_AttrKind_DLLExport:
5711+
case CX_AttrKind.CX_AttrKind_DLLImport:
5712+
{
5713+
// Nothing to handle
5714+
break;
5715+
}
5716+
5717+
case CX_AttrKind.CX_AttrKind_Deprecated:
5718+
{
5719+
var attrText = GetSourceRangeContents(namedDecl.TranslationUnit.Handle, attr.Extent);
5720+
5721+
var textStart = attrText.IndexOf('"');
5722+
var textLength = attrText.LastIndexOf('"') - textStart;
5723+
5724+
if (textLength > 2)
5725+
{
5726+
var text = attrText.AsSpan(textStart + 1, textLength - 2);
5727+
outputBuilder.WriteCustomAttribute($"Obsolete(\"{text}\")");
5728+
}
5729+
else
5730+
{
5731+
outputBuilder.WriteCustomAttribute($"Obsolete");
5732+
}
5733+
break;
5734+
}
5735+
5736+
case CX_AttrKind.CX_AttrKind_MSNoVTable:
5737+
case CX_AttrKind.CX_AttrKind_MSAllocator:
5738+
case CX_AttrKind.CX_AttrKind_MaxFieldAlignment:
5739+
case CX_AttrKind.CX_AttrKind_SelectAny:
5740+
case CX_AttrKind.CX_AttrKind_Uuid:
5741+
{
5742+
// Nothing to handle
5743+
break;
5744+
}
5745+
5746+
default:
5747+
{
5748+
AddDiagnostic(DiagnosticLevel.Warning, $"Unsupported attribute: '{attr.KindSpelling}'. Generated bindings may be incomplete.", namedDecl);
5749+
break;
5750+
}
5751+
}
5752+
}
5753+
}
56855754
}
56865755

56875756
private string GetLibraryPath(string remappedName)

0 commit comments

Comments
 (0)