diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.Naming.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.Naming.cs new file mode 100644 index 00000000..21b25bd6 --- /dev/null +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.Naming.cs @@ -0,0 +1,709 @@ +// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; +using System.Text.RegularExpressions; +using ClangSharp.Abstractions; +using ClangSharp.CSharp; +using ClangSharp.Interop; +using ClangSharp.XML; +using static ClangSharp.Interop.CX_AttrKind; +using static ClangSharp.Interop.CX_CXXAccessSpecifier; +using static ClangSharp.Interop.CX_StmtClass; +using static ClangSharp.Interop.CX_UnaryExprOrTypeTrait; +using static ClangSharp.Interop.CXBinaryOperatorKind; +using static ClangSharp.Interop.CXCallingConv; +using static ClangSharp.Interop.CXDiagnosticSeverity; +using static ClangSharp.Interop.CXEvalResultKind; +using static ClangSharp.Interop.CXTemplateArgumentKind; +using static ClangSharp.Interop.CXTranslationUnit_Flags; +using static ClangSharp.Interop.CXTypeKind; +using static ClangSharp.Interop.CXUnaryOperatorKind; + +namespace ClangSharp; + +public sealed partial class PInvokeGenerator +{ + private string GetCursorName(NamedDecl namedDecl) + { + if (!_cursorNames.TryGetValue(namedDecl, out var nameString)) + { + nameString = namedDecl.Name.NormalizePath(); + var name = nameString.AsSpan(); + + // strip the prefix + if (name.StartsWith("enum ", StringComparison.Ordinal)) + { + name = name[5..]; + nameString = null; + } + else if (name.StartsWith("struct ", StringComparison.Ordinal)) + { + name = name[7..]; + nameString = null; + } + else if (name.StartsWith("union ", StringComparison.Ordinal)) + { + name = name[6..]; + nameString = null; + } + + var anonymousNameStartIndex = name.IndexOf("::(", StringComparison.Ordinal); + + if (anonymousNameStartIndex != -1) + { + anonymousNameStartIndex += 2; + name = name[anonymousNameStartIndex..]; + nameString = null; + } + + if (namedDecl is CXXConstructorDecl cxxConstructorDecl) + { + var parent = cxxConstructorDecl.Parent; + Debug.Assert(parent is not null); + + nameString = GetCursorName(parent); + name = nameString; + } + else if (namedDecl is CXXDestructorDecl cxxDestructorDecl) + { + var parent = cxxDestructorDecl.Parent; + Debug.Assert(parent is not null); + + nameString = $"~{GetCursorName(parent)}"; + name = nameString; + } + else if (name.IsWhiteSpace() || name.StartsWith('(')) + { +#if DEBUG + if (name.StartsWith('(')) + { + Debug.Assert(name.StartsWith("(anonymous enum at ", StringComparison.Ordinal) || + name.StartsWith("(anonymous struct at ", StringComparison.Ordinal) || + name.StartsWith("(anonymous union at ", StringComparison.Ordinal) || + name.StartsWith("(unnamed enum at ", StringComparison.Ordinal) || + name.StartsWith("(unnamed struct at ", StringComparison.Ordinal) || + name.StartsWith("(unnamed union at ", StringComparison.Ordinal) || + name.StartsWith("(unnamed at ", StringComparison.Ordinal)); + Debug.Assert(name.EndsWith(')')); + } +#endif + + if (namedDecl is TypeDecl typeDecl) + { + nameString = (typeDecl is TagDecl tagDecl) && tagDecl.Handle.IsAnonymous + ? GetAnonymousName(tagDecl, tagDecl.TypeForDecl.KindSpelling) + : GetTypeName(namedDecl, context: null, type: typeDecl.TypeForDecl, ignoreTransparentStructsWhereRequired: false, isTemplate: false, nativeTypeName: out _); + name = nameString; + } + else if (namedDecl is ParmVarDecl) + { + nameString = "param"; + name = nameString; + } + else if (namedDecl is FieldDecl fieldDecl) + { + nameString = GetAnonymousName(fieldDecl, fieldDecl.CursorKindSpelling); + name = nameString; + } + else + { + AddDiagnostic(DiagnosticLevel.Error, $"Unsupported anonymous named declaration: '{namedDecl.DeclKindName}'.", namedDecl); + } + } + + nameString ??= name.ToString(); + _cursorNames[namedDecl] = nameString; + } + + Debug.Assert(!string.IsNullOrWhiteSpace(nameString)); + return nameString; + } + + private string GetCursorQualifiedName(NamedDecl namedDecl, bool truncateParameters = false) + { + if (!_cursorQualifiedNames.TryGetValue((namedDecl, truncateParameters), out var qualifiedName)) + { + var parts = new Stack(); + Decl? decl = namedDecl; + + do + { + if (decl is NamedDecl parentNamedDecl) + { + parts.Push(parentNamedDecl); + } + + if ((decl.DeclContext is null) && (decl is CXXMethodDecl cxxMethodDecl)) + { + var cxxRecordDecl = cxxMethodDecl.ThisObjectType.AsCXXRecordDecl; + Debug.Assert(cxxRecordDecl is not null); + decl = cxxRecordDecl; + } + else + { + decl = (Decl?)decl.DeclContext; + } + } + while (decl is not null); + + var qualifiedNameBuilder = new StringBuilder(); + + var part = parts.Pop(); + + while (parts.Count != 0) + { + AppendNamedDecl(part, GetCursorName(part), qualifiedNameBuilder); + _ = qualifiedNameBuilder.Append("::"); + part = parts.Pop(); + } + + AppendNamedDecl(part, GetCursorName(part), qualifiedNameBuilder); + + qualifiedName = qualifiedNameBuilder.ToString(); + _cursorQualifiedNames[(namedDecl, truncateParameters)] = qualifiedName; + } + + Debug.Assert(!string.IsNullOrWhiteSpace(qualifiedName)); + return qualifiedName; + + void AppendFunctionParameters(CXType functionType, StringBuilder qualifiedName) + { + if (truncateParameters) + { + return; + } + + _ = qualifiedName.Append('('); + + if (functionType.NumArgTypes != 0) + { + _ = qualifiedName.Append(functionType.GetArgType(0).Spelling); + + for (uint i = 1; i < functionType.NumArgTypes; i++) + { + _ = qualifiedName.Append(','); + _ = qualifiedName.Append(' '); + _ = qualifiedName.Append(functionType.GetArgType(i).Spelling); + } + } + + _ = qualifiedName.Append(')'); + _ = qualifiedName.Append(':'); + + _ = qualifiedName.Append(functionType.ResultType.Spelling); + + if (functionType.ExceptionSpecificationType == CXCursor_ExceptionSpecificationKind.CXCursor_ExceptionSpecificationKind_NoThrow) + { + _ = qualifiedName.Append(' '); + _ = qualifiedName.Append("nothrow"); + } + } + + void AppendNamedDecl(NamedDecl namedDecl, string name, StringBuilder qualifiedName) + { + _ = qualifiedName.Append(name); + + if (namedDecl is FunctionDecl functionDecl) + { + AppendFunctionParameters(functionDecl.Type.Handle, qualifiedName); + } + else if (namedDecl is TemplateDecl templateDecl) + { + AppendTemplateParameters(templateDecl, qualifiedName); + + if (namedDecl is FunctionTemplateDecl functionTemplateDecl) + { + AppendFunctionParameters(functionTemplateDecl.Handle.Type, qualifiedName); + } + } + else if (namedDecl is ClassTemplateSpecializationDecl classTemplateSpecializationDecl) + { + AppendTemplateArguments(classTemplateSpecializationDecl, qualifiedName); + } + } + + void AppendTemplateArgument(TemplateArgument templateArgument, StringBuilder qualifiedName) + { + switch (templateArgument.Kind) + { + case CXTemplateArgumentKind_Type: + { + _ = qualifiedName.Append(templateArgument.AsType.AsString); + break; + } + + case CXTemplateArgumentKind_Integral: + { + _ = qualifiedName.Append(templateArgument.AsIntegral); + break; + } + + default: + { + _ = qualifiedName.Append('?'); + break; + } + } + } + + void AppendTemplateArguments(ClassTemplateSpecializationDecl classTemplateSpecializationDecl, StringBuilder qualifiedName) + { + if (truncateParameters) + { + return; + } + + _ = qualifiedName.Append('<'); + + var templateArgs = classTemplateSpecializationDecl.TemplateArgs; + + if (templateArgs.Any()) + { + AppendTemplateArgument(templateArgs[0], qualifiedName); + + for (var i = 1; i < templateArgs.Count; i++) + { + _ = qualifiedName.Append(','); + _ = qualifiedName.Append(' '); + AppendTemplateArgument(templateArgs[i], qualifiedName); + } + } + + _ = qualifiedName.Append('>'); + } + + void AppendTemplateParameters(TemplateDecl templateDecl, StringBuilder qualifiedName) + { + if (truncateParameters) + { + return; + } + + _ = qualifiedName.Append('<'); + + var templateParameters = templateDecl.TemplateParameters; + + if (templateParameters.Any()) + { + _ = qualifiedName.Append(templateParameters[0].Name); + + for (var i = 1; i < templateParameters.Count; i++) + { + _ = qualifiedName.Append(','); + _ = qualifiedName.Append(' '); + _ = qualifiedName.Append(templateParameters[i].Name); + } + } + + _ = qualifiedName.Append('>'); + } + } + + private static Expr GetExprAsWritten(Expr expr, bool removeParens) + { + do + { + if (expr is ImplicitCastExpr implicitCastExpr) + { + expr = implicitCastExpr.SubExprAsWritten; + } + else if (removeParens && (expr is ParenExpr parenExpr)) + { + expr = parenExpr.SubExpr; + } + else + { + return expr; + } + } + while (true); + } + + private uint GetOverloadIndex(CXXMethodDecl cxxMethodDeclToMatch) + { + if (!_overloadIndices.TryGetValue(cxxMethodDeclToMatch, out var index)) + { + var parent = cxxMethodDeclToMatch.Parent; + Debug.Assert(parent is not null); + + index = GetOverloadIndex(cxxMethodDeclToMatch, parent, baseIndex: 0); + _overloadIndices.Add(cxxMethodDeclToMatch, index); + } + return index; + + uint GetOverloadIndex(CXXMethodDecl cxxMethodDeclToMatch, CXXRecordDecl cxxRecordDecl, uint baseIndex) + { + var index = baseIndex; + + foreach (var cxxBaseSpecifier in cxxRecordDecl.Bases) + { + var baseCxxRecordDecl = GetRecordDecl(cxxBaseSpecifier); + index = GetOverloadIndex(cxxMethodDeclToMatch, baseCxxRecordDecl, index); + } + + foreach (var cxxMethodDecl in cxxRecordDecl.Methods.OrderBy((cxxmd) => cxxmd.VtblIndex)) + { + if (IsExcluded(cxxMethodDecl)) + { + continue; + } + else if (cxxMethodDecl == cxxMethodDeclToMatch) + { + break; + } + else if (cxxMethodDecl.Name == cxxMethodDeclToMatch.Name) + { + index++; + } + } + + return index; + } + } + + private uint GetOverloadCount(CXXMethodDecl cxxMethodDeclToMatch) + { + var parent = cxxMethodDeclToMatch.Parent; + Debug.Assert(parent is not null); + + return GetOverloadIndex(cxxMethodDeclToMatch, parent, baseCount: 0); + + uint GetOverloadIndex(CXXMethodDecl cxxMethodDeclToMatch, CXXRecordDecl cxxRecordDecl, uint baseCount) + { + var count = baseCount; + + foreach (var cxxBaseSpecifier in cxxRecordDecl.Bases) + { + var baseCxxRecordDecl = GetRecordDecl(cxxBaseSpecifier); + count = GetOverloadIndex(cxxMethodDeclToMatch, baseCxxRecordDecl, count); + } + + foreach (var cxxMethodDecl in cxxRecordDecl.Methods) + { + if (IsExcluded(cxxMethodDecl)) + { + continue; + } + else if (cxxMethodDecl.Name == cxxMethodDeclToMatch.Name) + { + count++; + } + } + + return count; + } + } + + private CXXRecordDecl GetRecordDecl(CXXBaseSpecifier cxxBaseSpecifier) + { + var baseType = cxxBaseSpecifier.Type; + + if (IsType(cxxBaseSpecifier, baseType, out var recordType)) + { + return (CXXRecordDecl)recordType.Decl; + } + + AddDiagnostic(DiagnosticLevel.Error, "Failed to retrieve record type for CXX base specifier. Falling back to referenced type.", cxxBaseSpecifier); + return (CXXRecordDecl)cxxBaseSpecifier.Referenced; + } + + private string GetRemappedCursorName(NamedDecl namedDecl) => GetRemappedCursorName(namedDecl, out _, skipUsing: false); + + private string GetRemappedCursorName(NamedDecl namedDecl, out string nativeTypeName, bool skipUsing) + { + nativeTypeName = GetCursorQualifiedName(namedDecl); + + var name = nativeTypeName; + var remappedName = GetRemappedName(name, namedDecl, tryRemapOperatorName: true, out var wasRemapped, skipUsing); + + if (wasRemapped) + { + return remappedName; + } + + name = GetCursorQualifiedName(namedDecl, truncateParameters: true); + remappedName = GetRemappedName(name, namedDecl, tryRemapOperatorName: true, out wasRemapped, skipUsing); + + if (wasRemapped) + { + return remappedName; + } + + name = GetCursorName(namedDecl); + remappedName = GetRemappedName(name, namedDecl, tryRemapOperatorName: true, out wasRemapped, skipUsing); + + if (wasRemapped) + { + return remappedName; + } + + if (namedDecl is CXXConstructorDecl cxxConstructorDecl) + { + var parent = cxxConstructorDecl.Parent; + Debug.Assert(parent is not null); + remappedName = GetRemappedCursorName(parent); + } + else if (namedDecl is CXXDestructorDecl) + { + remappedName = "Dispose"; + } + else if ((namedDecl is FieldDecl fieldDecl) && name.StartsWith("__AnonymousFieldDecl_", StringComparison.Ordinal)) + { + if (fieldDecl.Type.AsCXXRecordDecl?.IsAnonymousStructOrUnion == true) + { + // For fields of anonymous types, use the name of the type but clean off the type + // kind tag at the end. + var typeName = GetRemappedNameForAnonymousRecord(fieldDecl.Type.AsCXXRecordDecl); + var tagIndex = typeName.LastIndexOf("_e__", StringComparison.Ordinal); + Debug.Assert(typeName[0] == '_'); + Debug.Assert(tagIndex >= 0); + remappedName = typeName.Substring(1, tagIndex - 1); + } + else + { + remappedName = "Anonymous"; + + var parent = fieldDecl.Parent; + Debug.Assert(parent is not null); + + if (parent.AnonymousFields.Count > 1) + { + var index = parent.AnonymousFields.IndexOf(fieldDecl) + 1; + remappedName += index.ToString(CultureInfo.InvariantCulture); + } + } + } + else if ((namedDecl is RecordDecl recordDecl) && name.StartsWith("__AnonymousRecord_", StringComparison.Ordinal)) + { + remappedName = GetRemappedNameForAnonymousRecord(recordDecl); + } + + return remappedName; + } + + private static int GetAnonymousRecordIndex(RecordDecl recordDecl, RecordDecl parentRecordDecl) + { + var index = -1; + var parentAnonRecordCount = parentRecordDecl.AnonymousRecords.Count; + + if (parentAnonRecordCount != 0) + { + index = parentRecordDecl.AnonymousRecords.IndexOf(recordDecl); + + if (index != -1) + { + if (parentAnonRecordCount > 1) + { + index++; + } + + if (parentRecordDecl.Parent is RecordDecl grandparentRecordDecl) + { + var parentIndex = GetAnonymousRecordIndex(parentRecordDecl, grandparentRecordDecl); + + // We can't have the nested anonymous record have the same name as the parent + // so skip that index and just go one higher instead. This could still conflict + // with another anonymous record at a different level, but that is less likely + // and will still be unambiguous in total. + + if ((parentIndex == index) || ((parentIndex > 0) && (index > parentIndex))) + { + if (recordDecl.IsUnion == parentRecordDecl.IsUnion) + { + index++; + } + } + } + } + } + + return index; + } + + private string GetRemappedNameForAnonymousRecord(RecordDecl recordDecl) + { + if (recordDecl.Parent is RecordDecl parentRecordDecl) + { + var remappedNameBuilder = new StringBuilder(); + var matchingField = null as FieldDecl; + + if (!recordDecl.IsAnonymousStructOrUnion) + { + matchingField = parentRecordDecl.Fields.Where((fieldDecl) => { + var fieldType = fieldDecl.Type.CanonicalType; + + if (fieldType is ArrayType arrayType) + { + fieldType = arrayType.ElementType.CanonicalType; + } + + return fieldType == recordDecl.TypeForDecl.CanonicalType; + }).FirstOrDefault(); + } + + if ((matchingField is not null) && !matchingField.IsAnonymousField) + { + _ = remappedNameBuilder.Append('_'); + _ = remappedNameBuilder.Append(GetRemappedCursorName(matchingField)); + } + else + { + _ = remappedNameBuilder.Append("_Anonymous"); + + var index = GetAnonymousRecordIndex(recordDecl, parentRecordDecl); + + if (index != 0) + { + _ = remappedNameBuilder.Append(index); + } + } + + // Add the type kind tag. + _ = remappedNameBuilder.Append("_e__"); + _ = remappedNameBuilder.Append(recordDecl.IsUnion ? "Union" : "Struct"); + return remappedNameBuilder.ToString(); + } + else + { + return $"_Anonymous_e__{(recordDecl.IsUnion ? "Union" : "Struct")}"; + } + } + + private string GetRemappedName(string name, Cursor? cursor, bool tryRemapOperatorName, out bool wasRemapped, bool skipUsing = false) + => GetRemappedName(name, cursor, tryRemapOperatorName, out wasRemapped, skipUsing, skipUsingIfNotRemapped: skipUsing); + + private string GetRemappedName(string name, Cursor? cursor, bool tryRemapOperatorName, out bool wasRemapped, bool skipUsing, bool skipUsingIfNotRemapped) + { + var remappedNamesLookup = _config._remappedNames.GetAlternateLookup>(); + + if (remappedNamesLookup.TryGetValue(name, out var remappedName)) + { + wasRemapped = true; + _ = _usedRemappings.Add(name); + return AddUsingDirectiveIfNeeded(_outputBuilder, remappedName, skipUsing); + } + + if (name.StartsWith("const ", StringComparison.Ordinal)) + { + var tmpName = name.AsSpan()[6..]; + + if (remappedNamesLookup.TryGetValue(tmpName, out remappedName)) + { + + wasRemapped = true; + _ = _usedRemappings.Add(tmpName.ToString()); + return AddUsingDirectiveIfNeeded(_outputBuilder, remappedName, skipUsing); + } + } + + remappedName = name; + + if ((cursor is FunctionDecl functionDecl) && tryRemapOperatorName && TryRemapOperatorName(ref remappedName, functionDecl)) + { + wasRemapped = true; + // We don't track remapped operators in _usedRemappings + return AddUsingDirectiveIfNeeded(_outputBuilder, remappedName, skipUsing); + } + + if ((cursor is CXXBaseSpecifier cxxBaseSpecifier) && remappedName.StartsWith("__AnonymousBase_", StringComparison.Ordinal)) + { + Debug.Assert(_cxxRecordDeclContext is not null); + remappedName = "Base"; + + if (_cxxRecordDeclContext.Bases.Count > 1) + { + var index = _cxxRecordDeclContext.Bases.IndexOf(cxxBaseSpecifier) + 1; + remappedName += index.ToString(CultureInfo.InvariantCulture); + } + + wasRemapped = true; + return AddUsingDirectiveIfNeeded(_outputBuilder, remappedName, skipUsing); + } + + wasRemapped = false; + return AddUsingDirectiveIfNeeded(_outputBuilder, remappedName, skipUsingIfNotRemapped); + + string AddUsingDirectiveIfNeeded(IOutputBuilder? outputBuilder, string remappedName, bool skipUsing) + { + if (!skipUsing) + { + if (NeedsSystemSupportRegex().IsMatch(remappedName)) + { + outputBuilder?.EmitSystemSupport(); + } + + var namespaceName = GetNamespace(remappedName); + AddUsingDirective(outputBuilder, namespaceName); + } + + return remappedName; + } + } + + private string GetRemappedTypeName(Cursor? cursor, Cursor? context, Type type, out string nativeTypeName, bool skipUsing = false, bool ignoreTransparentStructsWhereRequired = false, bool isTemplate = false) + { + var name = GetTypeName(cursor, context, type, ignoreTransparentStructsWhereRequired, isTemplate: isTemplate, nativeTypeName: out nativeTypeName); + + var nameToCheck = nativeTypeName; + var remappedName = GetRemappedName(nameToCheck, cursor, tryRemapOperatorName: false, out var wasRemapped, skipUsing, skipUsingIfNotRemapped: true); + + if (!wasRemapped) + { + nameToCheck = name; + remappedName = GetRemappedName(nameToCheck, cursor, tryRemapOperatorName: false, out wasRemapped, skipUsing); + + if (!wasRemapped) + { + if (IsTypeConstantOrIncompleteArray(cursor, type, out var arrayType) && IsType(cursor, arrayType.ElementType)) + { + type = arrayType.ElementType; + } + + if (IsType(cursor, type, out var recordType) && remappedName.StartsWith("__AnonymousRecord_", StringComparison.Ordinal)) + { + var recordDecl = recordType.Decl; + remappedName = GetRemappedNameForAnonymousRecord(recordDecl); + } + else if (IsType(cursor, type, out var enumType) && remappedName.StartsWith("__AnonymousEnum_", StringComparison.Ordinal)) + { + remappedName = GetRemappedTypeName(enumType.Decl, context: null, enumType.Decl.IntegerType, out _, skipUsing); + } + else if (cursor is EnumDecl enumDecl) + { + // Even though some types have entries with names like *_FORCE_DWORD or *_FORCE_UINT + // MSVC and Clang both still treat this as "signed" values and thus we don't want + // to specially handle it as uint, as that can break ABI handling on some platforms. + + WithType(enumDecl, ref remappedName, ref nativeTypeName); + } + } + } + + if (string.IsNullOrWhiteSpace(nativeTypeName)) + { + // When we have an empty native type name, it means the original + // name is the same as the native type name and no adjustments + // were made. In order to ensure things are correctly preserved + // we need to ensure its propagated back here so the below comparison + // works and we don't end up comparing "empty" vs "remapped" + nativeTypeName = name; + } + + if (IsNativeTypeNameEquivalent(nativeTypeName, remappedName)) + { + // Empty the native type name if its equivalent to the new name + nativeTypeName = string.Empty; + } + + return remappedName; + } +} diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.Predicates.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.Predicates.cs new file mode 100644 index 00000000..7270c301 --- /dev/null +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.Predicates.cs @@ -0,0 +1,1628 @@ +// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; +using System.Text.RegularExpressions; +using ClangSharp.Abstractions; +using ClangSharp.CSharp; +using ClangSharp.Interop; +using ClangSharp.XML; +using static ClangSharp.Interop.CX_AttrKind; +using static ClangSharp.Interop.CX_CXXAccessSpecifier; +using static ClangSharp.Interop.CX_StmtClass; +using static ClangSharp.Interop.CX_UnaryExprOrTypeTrait; +using static ClangSharp.Interop.CXBinaryOperatorKind; +using static ClangSharp.Interop.CXCallingConv; +using static ClangSharp.Interop.CXDiagnosticSeverity; +using static ClangSharp.Interop.CXEvalResultKind; +using static ClangSharp.Interop.CXTemplateArgumentKind; +using static ClangSharp.Interop.CXTranslationUnit_Flags; +using static ClangSharp.Interop.CXTypeKind; +using static ClangSharp.Interop.CXUnaryOperatorKind; + +namespace ClangSharp; + +public sealed partial class PInvokeGenerator +{ + private static bool IsEnumOperator(FunctionDecl functionDecl, string name) + { + if (name.StartsWith("operator", StringComparison.Ordinal) && ((functionDecl.Parameters.Count == 1) || (functionDecl.Parameters.Count == 2))) + { + var parmVarDecl1 = functionDecl.Parameters[0]; + var parmVarDecl1Type = parmVarDecl1.Type; + + if (IsType(parmVarDecl1, parmVarDecl1Type, out var pointerType1)) + { + parmVarDecl1Type = pointerType1.PointeeType; + } + else if (IsType(parmVarDecl1, parmVarDecl1Type, out var referenceType1)) + { + parmVarDecl1Type = referenceType1.PointeeType; + } + + if (functionDecl.Parameters.Count == 1) + { + return IsType(parmVarDecl1); + } + + var parmVarDecl2 = functionDecl.Parameters[1]; + var parmVarDecl2Type = parmVarDecl2.Type; + + if (IsType(parmVarDecl2, parmVarDecl2Type, out var pointerType2)) + { + parmVarDecl2Type = pointerType2.PointeeType; + } + else if (IsType(parmVarDecl2, parmVarDecl2Type, out var referenceType2)) + { + parmVarDecl2Type = referenceType2.PointeeType; + } + + if ((parmVarDecl1Type.CanonicalType == parmVarDecl2Type.CanonicalType) && IsType(parmVarDecl2)) + { + return true; + } + } + return false; + } + + private bool IsExcluded(Cursor cursor) => IsExcluded(cursor, out _); + + private bool IsExcluded(Cursor cursor, out bool isExcludedByConflictingDefinition) + { + if (!_isExcluded.TryGetValue(cursor, out var isExcludedValue)) + { + isExcludedValue |= (!IsAlwaysIncluded(cursor) && (IsExcludedByConfig(cursor) || IsExcludedByFile(cursor) || IsExcludedByName(cursor, ref isExcludedValue) || IsExcludedByAttributes(cursor))) ? 0b01u : 0b00u; + _isExcluded.Add(cursor, isExcludedValue); + } + isExcludedByConflictingDefinition = (isExcludedValue & 0b10) != 0; + return (isExcludedValue & 0b01) != 0; + + bool IsAlwaysIncluded(Cursor cursor) + { + return (cursor is TranslationUnitDecl) || (cursor is LinkageSpecDecl) || (cursor is NamespaceDecl) || ((cursor is VarDecl varDecl) && varDecl.Name.StartsWith("ClangSharpMacro_", StringComparison.Ordinal)); + } + + bool IsExcludedByConfig(Cursor cursor) + { + return (_config.ExcludeFunctionsWithBody && (cursor is FunctionDecl functionDecl) && functionDecl.HasBody) + || (!_config.GenerateTemplateBindings && ((cursor is TemplateDecl) || (cursor is ClassTemplateSpecializationDecl))); + } + + bool IsExcludedByFile(Cursor cursor) + { + if (_outputBuilder != null) + { + // We don't want to exclude by file if we already have an active output builder as we + // are likely processing members of an already included type but those members may + // indirectly exist or be defined in a non-traversed file. + return false; + } + + var declLocation = cursor.Location; + declLocation.GetFileLocation(out var file, out var line, out var column, out _); + + if (IsIncludedFileOrLocation(cursor, file, declLocation)) + { + return false; + } + + // It is not uncommon for some declarations to be done using macros, which are themselves + // defined in an imported header file. We want to also check if the expansion location is + // in the main file to catch these cases and ensure we still generate bindings for them. + + declLocation.GetExpansionLocation(out var expansionFile, out var expansionLine, out var expansionColumn, out _); + + if ((expansionFile == file) && (expansionLine == line) && (expansionColumn == column) && _config.TraversalNames.Count != 0) + { + // clang_getLocation is a very expensive call, so exit early if the expansion file is the same + // However, if we are not explicitly specifying traversal names, its possible the expansion location + // is the same, but IsMainFile is now marked as true, in which case we can't exit early. + + return true; + } + + var expansionLocation = cursor.TranslationUnit.Handle.GetLocation(expansionFile, expansionLine, expansionColumn); + + return !IsIncludedFileOrLocation(cursor, file, expansionLocation); + } + + bool IsExcludedByName(Cursor cursor, ref uint isExcludedValue) + { + var isExcludedByConfigOption = false; + var qualifiedNameWithoutParameters = ""; + + string qualifiedName; + string name; + string kind; + + if (cursor is NamedDecl namedDecl) + { + // We get the non-remapped name for the purpose of exclusion checks to ensure that users + // can remove no-definition declarations in favor of remapped anonymous declarations. + + qualifiedName = GetCursorQualifiedName(namedDecl); + + if (namedDecl is FunctionDecl) + { + qualifiedNameWithoutParameters = GetCursorQualifiedName(namedDecl, truncateParameters: true); + } + + name = GetCursorName(namedDecl); + kind = $"{namedDecl.DeclKindName} declaration"; + + if ((namedDecl is TagDecl tagDecl) && (tagDecl.Definition != tagDecl) && (tagDecl.Definition != null)) + { + // We don't want to generate bindings for anything + // that is not itself a definition and that has a + // definition that can be resolved. This ensures we + // still generate bindings for things which are used + // as opaque handles, but which aren't ever defined. + + if (_config.LogExclusions) + { + AddDiagnostic(DiagnosticLevel.Info, $"Excluded {kind} '{qualifiedName}' by as it is not a definition."); + } + return true; + } + } + else if (cursor is MacroDefinitionRecord macroDefinitionRecord) + { + qualifiedName = macroDefinitionRecord.Name; + name = macroDefinitionRecord.Name; + kind = macroDefinitionRecord.CursorKindSpelling; + } + else + { + return false; + } + + if (qualifiedName.Contains("ClangSharpMacro_", StringComparison.Ordinal)) + { + qualifiedName = qualifiedName.Replace("ClangSharpMacro_", "", StringComparison.Ordinal); + } + + if (name.Contains("ClangSharpMacro_", StringComparison.Ordinal)) + { + name = name.Replace("ClangSharpMacro_", "", StringComparison.Ordinal); + } + + if (cursor is RecordDecl recordDecl) + { + if (_config.ExcludeEmptyRecords && IsEmptyRecord(recordDecl)) + { + isExcludedByConfigOption = true; + } + } + else if (cursor is FunctionDecl functionDecl) + { + if (_config.ExcludeComProxies && IsComProxy(functionDecl, name)) + { + isExcludedByConfigOption = true; + } + else if (_config.ExcludeEnumOperators && IsEnumOperator(functionDecl, name)) + { + isExcludedByConfigOption = true; + } + else if (functionDecl is CXXMethodDecl cxxMethodDecl) + { + var parent = cxxMethodDecl.Parent; + Debug.Assert(parent is not null); + + if (IsConflictingMethodDecl(cxxMethodDecl, parent)) + { + isExcludedValue |= 0b10; + } + } + + if (_config.GenerateDisableRuntimeMarshalling && functionDecl.IsVariadic) + { + isExcludedByConfigOption = true; + } + } + + if (_config.ExcludedNames.Contains(qualifiedName)) + { + if (_config.LogExclusions) + { + var message = $"Excluded {kind} '{qualifiedName}' by exact match"; + + if (isExcludedByConfigOption) + { + message += "; Exclusion is unnecessary due to a config option"; + } + else if ((isExcludedValue & 0b10) != 0) + { + message += "; Exclusion is unnecessary due to a conflicting definition"; + } + + AddDiagnostic(DiagnosticLevel.Info, message); + } + return true; + } + + if (_config.ExcludedNames.Contains(qualifiedNameWithoutParameters) || _config.ExcludedNames.Contains(name)) + { + if (_config.LogExclusions) + { + var message = $"Excluded {kind} '{qualifiedName}' by partial match against {name}"; + + if (isExcludedByConfigOption) + { + message += "; Exclusion is unnecessary due to a config option"; + } + else if ((isExcludedValue & 0b10) != 0) + { + message += "; Exclusion is unnecessary due to a conflicting definition"; + } + + AddDiagnostic(DiagnosticLevel.Info, message); + } + return true; + } + + if (isExcludedByConfigOption) + { + if (_config.LogExclusions) + { + AddDiagnostic(DiagnosticLevel.Info, $"Excluded {kind} '{qualifiedName}' by config option"); + } + return true; + } + + if (_config.IncludedNames.Count != 0 && !_config.IncludedNames.Contains(qualifiedName) + && !_config.IncludedNames.Contains(qualifiedNameWithoutParameters) + && !_config.IncludedNames.Contains(name)) + { + var semanticParentCursor = cursor.SemanticParentCursor; + + if ((semanticParentCursor is null) || IsExcluded(semanticParentCursor) || IsAlwaysIncluded(semanticParentCursor)) + { + if (_config.LogExclusions) + { + AddDiagnostic(DiagnosticLevel.Info, $"Excluded {kind} '{qualifiedName}' as it was not in the include list"); + } + return true; + } + } + + if ((isExcludedValue & 0b10) != 0) + { + if (_config.LogExclusions) + { + AddDiagnostic(DiagnosticLevel.Info, $"Excluded {kind} '{qualifiedName}' by conflicting definition"); + } + return true; + } + + return false; + } + + bool IsIncludedFileOrLocation(Cursor cursor, CXFile file, CXSourceLocation location) + { + // Use case insensitive comparison on Windows + var equalityComparer = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal; + + // Normalize paths to be '/' for comparison + var fileName = file.Name.ToString().NormalizePath(); + + if (_visitedFiles.Add(fileName) && _config.LogVisitedFiles) + { + AddDiagnostic(DiagnosticLevel.Info, $"Visiting {fileName}"); + } + + if (_config.TraversalNames.Contains(fileName, equalityComparer)) + { + return true; + } + else if (_config.TraversalNames.Contains(fileName.NormalizeFullPath(), equalityComparer)) + { + return true; + } + else if (_config.TraversalNames.Count == 0 && location.IsFromMainFile) + { + return true; + } + + return false; + } + + bool IsComProxy(FunctionDecl functionDecl, string name) + { + var parmVarDecl = null as ParmVarDecl; + + if (name.EndsWith("_UserFree", StringComparison.Ordinal) || name.EndsWith("_UserFree64", StringComparison.Ordinal) || + name.EndsWith("_UserMarshal", StringComparison.Ordinal) || name.EndsWith("_UserMarshal64", StringComparison.Ordinal) || + name.EndsWith("_UserSize", StringComparison.Ordinal) || name.EndsWith("_UserSize64", StringComparison.Ordinal) || + name.EndsWith("_UserUnmarshal", StringComparison.Ordinal) || name.EndsWith("_UserUnmarshal64", StringComparison.Ordinal)) + { + var parameters = functionDecl.Parameters; + parmVarDecl = (parameters.Count != 0) ? parameters[^1] : null; + } + else if (name.EndsWith("_Proxy", StringComparison.Ordinal) || name.EndsWith("_Stub", StringComparison.Ordinal)) + { + var parameters = functionDecl.Parameters; + parmVarDecl = (parameters.Count != 0) ? parameters[0] : null; + } + + if ((parmVarDecl is not null) && IsType(parmVarDecl, out var pointerType)) + { + var typeName = GetTypeName(parmVarDecl, context: null, type: pointerType.PointeeType, ignoreTransparentStructsWhereRequired: false, isTemplate: false, nativeTypeName: out var nativeTypeName); + return name.StartsWith($"{nativeTypeName}_", StringComparison.Ordinal) || name.StartsWith($"{typeName}_", StringComparison.Ordinal) || typeName.Equals("IRpcStubBuffer", StringComparison.Ordinal); + } + return false; + } + + bool IsConflictingMethodDecl(CXXMethodDecl cxxMethodDeclToMatch, CXXRecordDecl cxxRecordDecl) + { + var cxxMethodDeclToMatchName = GetRemappedCursorName(cxxMethodDeclToMatch); + var foundCxxMethodDeclToMatch = false; + + foreach (var cxxBaseSpecifier in cxxRecordDecl.Bases) + { + var baseCxxRecordDecl = GetRecordDecl(cxxBaseSpecifier); + + if (ContainsConflictingMethodDecl(cxxMethodDeclToMatch, cxxRecordDecl, baseCxxRecordDecl, cxxMethodDeclToMatchName, ref foundCxxMethodDeclToMatch)) + { + return true; + } + } + + return ContainsConflictingMethodDecl(cxxMethodDeclToMatch, cxxRecordDecl, cxxRecordDecl, cxxMethodDeclToMatchName, ref foundCxxMethodDeclToMatch); + + bool ContainsConflictingMethodDecl(CXXMethodDecl cxxMethodDeclToMatch, CXXRecordDecl rootCxxRecordDecl, CXXRecordDecl cxxRecordDecl, string cxxMethodDeclToMatchName, ref bool foundCxxMethodDeclToMatch) + { + var cxxMethodDecls = cxxRecordDecl.Methods; + + if (cxxMethodDecls.Count != 0) + { + foreach (var cxxMethodDecl in cxxMethodDecls.OrderBy((cxxmd) => cxxmd.VtblIndex)) + { + if (IsConflictingMethodDecl(cxxMethodDeclToMatch, cxxMethodDecl, rootCxxRecordDecl, cxxRecordDecl, cxxMethodDeclToMatchName, ref foundCxxMethodDeclToMatch)) + { + return true; + } + } + } + + return false; + } + + bool IsConflictingMethodDecl(CXXMethodDecl cxxMethodDeclToMatch, CXXMethodDecl cxxMethodDecl, CXXRecordDecl rootCxxRecordDecl, CXXRecordDecl cxxRecordDecl, string cxxMethodDeclToMatchName, ref bool foundCxxMethodDeclToMatch) + { + var methodName = GetRemappedCursorName(cxxMethodDecl); + + if (cxxMethodDeclToMatchName != methodName) + { + return false; + } + + if (cxxMethodDecl == cxxMethodDeclToMatch) + { + foundCxxMethodDeclToMatch = true; + return false; + } + + if (cxxMethodDecl.Parameters.Count != cxxMethodDeclToMatch.Parameters.Count) + { + return false; + } + + var allMatch = true; + + for (var n = 0; n < cxxMethodDeclToMatch.Parameters.Count; n++) + { + var parameterTypeToMatch = cxxMethodDeclToMatch.Parameters[n].Type; + var parameterType = cxxMethodDecl.Parameters[n].Type; + + if (parameterType.CanonicalType == parameterTypeToMatch.CanonicalType) + { + continue; + } + + if (IsType(cursor, parameterTypeToMatch, out var pointerTypeToMatch) && + IsType(cursor, parameterType, out var referenceType) && + (referenceType.PointeeType.CanonicalType == pointerTypeToMatch.PointeeType.CanonicalType)) + { + continue; + } + + if (IsType(cursor, parameterTypeToMatch, out var referenceTypeToMatch) && + IsType(cursor, parameterType, out var pointerType) && + (pointerType.PointeeType.CanonicalType == referenceTypeToMatch.PointeeType.CanonicalType)) + { + continue; + } + + allMatch = false; + break; + } + + if (!allMatch) + { + return false; + } + + if (cxxMethodDecl.IsVirtual) + { + if (cxxMethodDeclToMatch.IsVirtual) + { + if (rootCxxRecordDecl != cxxRecordDecl) + { + // The found declaration and declaration to match are both virtual + // We want to treat the one from the base declaration as non-conflicting + // So return true to report the declaration to match as the conflict + return true; + } + else if (cxxMethodDeclToMatch.IsThisDeclarationADefinition != cxxMethodDecl.IsThisDeclarationADefinition) + { + return false; + } + else + { + AddDiagnostic(DiagnosticLevel.Error, "Found conflicting method definitions for two virtual methods.", cxxMethodDeclToMatch); + } + } + else + { + // The found declaration is virtual while the declaration to match is not + // We want to treat the virtual declaration as non-conflicting + // So return true to report the declaration to match as the conflict + return true; + } + } + else if (cxxMethodDeclToMatch.IsVirtual) + { + // The declaration to match is virtual while the found declaration is not + // We want to treat the virtual declaration as non-conflicting + // So treat the declaration as non-conflicting and continue searching + return false; + } + else + { + // Neither the declaration nor the declaration to match are virtual + // We want to pick whichever declaration appears first + // So return true or false based on if we already encountered the declaration to match + return !foundCxxMethodDeclToMatch; + } + + return false; + } + } + + bool IsEmptyRecord(RecordDecl recordDecl) + { + if (recordDecl.Fields.Count != 0) + { + if (!GetCursorName(recordDecl).EndsWith("__", StringComparison.Ordinal) || (recordDecl.Fields.Count != 1)) + { + return false; + } + + var field = recordDecl.Fields[0]; + + if (!GetCursorName(field).Equals("unused", StringComparison.Ordinal) || !IsType(field, out var builtinType) || (builtinType.Kind != CXType_Int)) + { + return false; + } + } + + foreach (var decl in recordDecl.Decls) + { + if ((decl is RecordDecl nestedRecordDecl) && nestedRecordDecl.IsAnonymousStructOrUnion && !IsEmptyRecord(nestedRecordDecl)) + { + return false; + } + + if ((decl is CXXMethodDecl cxxMethodDecl) && cxxMethodDecl.IsVirtual) + { + return false; + } + } + + if (recordDecl is CXXRecordDecl cxxRecordDecl) + { + foreach (var cxxBaseSpecifier in cxxRecordDecl.Bases) + { + var baseCxxRecordDecl = GetRecordDecl(cxxBaseSpecifier); + + if (!IsEmptyRecord(baseCxxRecordDecl)) + { + return false; + } + } + } + + return !TryGetUuid(recordDecl, out _); + } + + bool IsExcludedByAttributes(Cursor cursor) + { + if (cursor is NamedDecl namedDecl) + { + foreach (var attr in GetAttributesFor(namedDecl)) + { + switch (attr.Kind) + { + case CX_AttrKind_Builtin: + return true; + } + } + } + + return false; + } + } + + private bool IsBaseExcluded(CXXRecordDecl cxxRecordDecl, CXXRecordDecl baseCxxRecordDecl, CXXBaseSpecifier cxxBaseSpecifier, out string baseFieldName) + { + baseFieldName = GetAnonymousName(cxxBaseSpecifier, "Base"); + baseFieldName = GetRemappedName(baseFieldName, cxxBaseSpecifier, tryRemapOperatorName: true, out _, skipUsing: true); + + var qualifiedName = $"{GetCursorQualifiedName(cxxRecordDecl)}::{baseFieldName}"; + return _config.ExcludedNames.Contains(qualifiedName); + } + + private bool IsFixedSize(Cursor cursor, Type type) + { + // We don't want to handle these using IsType because we need to specially + // handle cases like TypedefType at each level of the type hierarchy + + if (type is ArrayType) + { + return false; + } + else if (type is AttributedType attributedType) + { + return IsFixedSize(cursor, attributedType.ModifiedType); + } + else if (type is BuiltinType) + { + return true; + } + else if (type is DecltypeType decltypeType) + { + return IsFixedSize(cursor, decltypeType.UnderlyingType); + } + else if (type is ElaboratedType elaboratedType) + { + return IsFixedSize(cursor, elaboratedType.NamedType); + } + else if (type is EnumType enumType) + { + return IsFixedSize(cursor, enumType.Decl.IntegerType); + } + else if (type is FunctionType) + { + return false; + } + else if (type is PointerType) + { + return false; + } + else if (type is RecordType recordType) + { + var recordDecl = recordType.Decl; + + return recordDecl.Fields.All((fieldDecl) => IsFixedSize(fieldDecl, fieldDecl.Type)) + && (recordDecl is not CXXRecordDecl cxxRecordDecl || cxxRecordDecl.Methods.All((cxxMethodDecl) => !cxxMethodDecl.IsVirtual)); + } + else if (type is ReferenceType) + { + return false; + } + else if (type is TypedefType typedefType) + { + var name = GetTypeName(cursor, context: null, type: type, ignoreTransparentStructsWhereRequired: false, isTemplate: false, nativeTypeName: out _); + var remappedName = GetRemappedTypeName(cursor, context: null, type, out _, skipUsing: true, ignoreTransparentStructsWhereRequired: false); + + return !remappedName.Equals("IntPtr", StringComparison.Ordinal) + && !remappedName.Equals("nint", StringComparison.Ordinal) + && !remappedName.Equals("nuint", StringComparison.Ordinal) + && !remappedName.Equals("UIntPtr", StringComparison.Ordinal) + && IsFixedSize(cursor, typedefType.Decl.UnderlyingType); + } + else + { + AddDiagnostic(DiagnosticLevel.Warning, $"Unsupported type: '{type.TypeClass}'. Assuming unfixed size.", cursor); + return false; + } + } + + private static bool IsNativeTypeNameEquivalent(string nativeTypeName, string typeName) + { + return nativeTypeName.Equals(typeName, StringComparison.OrdinalIgnoreCase) + || nativeTypeName.Replace(" ", "", StringComparison.Ordinal).Equals(typeName, StringComparison.OrdinalIgnoreCase); + } + + private bool IsPrevContextDecl([MaybeNullWhen(false)] out T cursor, out object? userData, bool includeLast = false) + where T : Decl + { + var previousContext = _context.Last; + Debug.Assert(previousContext != null); + + if (!includeLast) + { + previousContext = previousContext.Previous; + Debug.Assert(previousContext != null); + } + + while (previousContext.Value.Cursor is not Decl) + { + previousContext = previousContext.Previous; + Debug.Assert(previousContext != null); + } + + var value = previousContext.Value; + + if (value.Cursor is T t) + { + cursor = t; + userData = value.UserData; + return true; + } + else + { + cursor = null; + userData = null; + return false; + } + } + + private bool IsPrevContextStmt([MaybeNullWhen(false)] out T cursor, out object? userData, bool preserveParen = false, bool preserveImplicitCast = false) + where T : Stmt + { + var previousContext = _context.Last; + Debug.Assert(previousContext != null); + + do + { + previousContext = previousContext.Previous; + Debug.Assert(previousContext is not null); + } + while ((!preserveParen && (previousContext.Value.Cursor is ParenExpr)) || (!preserveImplicitCast && (previousContext.Value.Cursor is ImplicitCastExpr))); + + var value = previousContext.Value; + + if (value.Cursor is T t) + { + cursor = t; + userData = value.UserData; + return true; + } + else + { + cursor = null; + userData = null; + return false; + } + } + + private bool IsReadonly(CXXMethodDecl? cxxMethodDecl) + { + if (cxxMethodDecl is not null) + { + return cxxMethodDecl.IsConst || HasRemapping(cxxMethodDecl, _config._withReadonlys, matchStar: true); + } + return false; + } + + private static bool IsStmtAsWritten(Cursor cursor, [MaybeNullWhen(false)] out T value, bool removeParens = false) + where T : Stmt + { + if (cursor is Expr expr) + { + cursor = GetExprAsWritten(expr, removeParens); + } + + if (cursor is T t) + { + value = t; + return true; + } + else + { + value = null; + return false; + } + } + + private static bool IsStmtAsWritten(Stmt stmt, Stmt expectedStmt, bool removeParens = false) + { + if (stmt == expectedStmt) + { + return true; + } + + if (stmt is not Expr expr) + { + return false; + } + + expr = GetExprAsWritten(expr, removeParens); + return expr == expectedStmt; + } + + private static bool IsType(Expr expr) + where T : Type => IsType(expr, out _); + + private static bool IsType(Expr expr, [MaybeNullWhen(false)] out T value) + where T : Type => IsType(expr, expr.Type, out value); + + private static bool IsType(ValueDecl valueDecl) + where T : Type => IsType(valueDecl, out _); + + private static bool IsType(ValueDecl typeDecl, [MaybeNullWhen(false)] out T value) + where T : Type => IsType(typeDecl, typeDecl.Type, out value); + + private static bool IsType(Cursor? cursor, Type type) + where T : Type => IsType(cursor, type, out _); + + private static bool IsType(Cursor? cursor, Type type, [MaybeNullWhen(false)] out T value) + where T : Type + { + if (type is T t) + { + value = t; + return true; + } + else if (type is AttributedType attributedType) + { + return IsType(cursor, attributedType.ModifiedType, out value); + } + else if (type is DecltypeType decltypeType) + { + return IsType(cursor, decltypeType.UnderlyingType, out value); + } + else if (type is DeducedType deducedType) + { + return IsType(cursor, deducedType.GetDeducedType, out value); + } + else if (type is DependentNameType dependentNameType) + { + if (dependentNameType.IsSugared) + { + return IsType(cursor, dependentNameType.Desugar, out value); + } + } + else if (type is ElaboratedType elaboratedType) + { + return IsType(cursor, elaboratedType.NamedType, out value); + } + else if (type is InjectedClassNameType injectedClassNameType) + { + return IsType(cursor, injectedClassNameType.InjectedTST, out value); + } + else if (type is PackExpansionType packExpansionType) + { + return IsType(cursor, packExpansionType.Pattern, out value); + } + else if (type is SubstTemplateTypeParmType substTemplateTypeParmType) + { + return IsType(cursor, substTemplateTypeParmType.ReplacementType, out value); + } + else if (type is TemplateSpecializationType templateSpecializationType) + { + if (templateSpecializationType.IsTypeAlias) + { + return IsType(cursor, templateSpecializationType.AliasedType, out value); + } + else if (templateSpecializationType.IsSugared) + { + return IsType(cursor, templateSpecializationType.Desugar, out value); + } + else if (templateSpecializationType.TemplateName.AsTemplateDecl is TemplateDecl templateDecl) + { + // We exclude InjectedClassNameType here to avoid infinite recursion. + if ((templateDecl.TemplatedDecl is TypeDecl typeDecl) && (typeDecl.TypeForDecl is not InjectedClassNameType )) + { + return IsType(cursor, typeDecl.TypeForDecl, out value); + } + } + } + else if (type is TemplateTypeParmType templateTypeParmType) + { + if (templateTypeParmType.IsSugared) + { + return IsType(cursor, templateTypeParmType.Decl.TypeForDecl, out value); + } + } + else if (type is TypedefType typedefType) + { + return IsType(cursor, typedefType.Decl.UnderlyingType, out value); + } + else if (type is UsingType usingType) + { + if (usingType.IsSugared) + { + return IsType(cursor, usingType.Desugar, out value); + } + } + + value = default; + return false; + } + + private static bool IsTypeConstantOrIncompleteArray(Expr expr) + => IsTypeConstantOrIncompleteArray(expr, out _); + + private static bool IsTypeConstantOrIncompleteArray(Expr expr, [MaybeNullWhen(false)] out ArrayType arrayType) + => IsTypeConstantOrIncompleteArray(expr, expr.Type, out arrayType); + + private bool IsTypeConstantOrIncompleteArray(ValueDecl valueDecl) + => IsTypeConstantOrIncompleteArray(valueDecl, out _); + + private static bool IsTypeConstantOrIncompleteArray(ValueDecl valueDecl, [MaybeNullWhen(false)] out ArrayType arrayType) + => IsTypeConstantOrIncompleteArray(valueDecl, valueDecl.Type, out arrayType); + + private static bool IsTypeConstantOrIncompleteArray(Cursor? cursor, Type type) + => IsTypeConstantOrIncompleteArray(cursor, type, out _); + + private static bool IsTypeConstantOrIncompleteArray(Cursor? cursor, Type type, [MaybeNullWhen(false)] out ArrayType arrayType) + => IsType(cursor, type, out arrayType) + && (arrayType is ConstantArrayType or IncompleteArrayType); + + private static bool IsTypePointerOrReference(Expr expr) + => IsTypePointerOrReference(expr, expr.Type); + + private static bool IsTypePointerOrReference(ValueDecl valueDecl) + => IsTypePointerOrReference(valueDecl, valueDecl.Type); + + private static bool IsTypePointerOrReference(Cursor? cursor, Type type) + => IsType(cursor, type) + || IsType(cursor, type); + + private static bool IsTypeVoid(Cursor? cursor, Type type) + => IsType(cursor, type, out var builtinType) + && (builtinType.Kind == CXType_Void); + + internal bool IsSupportedFixedSizedBufferType(string typeName) + { + switch (typeName) + { + case "bool": + case "byte": + case "char": + case "double": + case "float": + case "int": + case "long": + case "sbyte": + case "short": + case "ushort": + case "uint": + case "ulong": + { + // We want to prefer InlineArray in modern code, as it is safer and supports more features + return Config.GenerateCompatibleCode; + } + + default: + { + return false; + } + } + } + + private static bool IsTransparentStructBoolean(PInvokeGeneratorTransparentStructKind kind) + => kind is PInvokeGeneratorTransparentStructKind.Boolean; + + private static bool IsTransparentStructHandle(PInvokeGeneratorTransparentStructKind kind) + => kind is PInvokeGeneratorTransparentStructKind.Handle + or PInvokeGeneratorTransparentStructKind.HandleWin32; + + private static bool IsTransparentStructHexBased(PInvokeGeneratorTransparentStructKind kind) + => IsTransparentStructHandle(kind) + || (kind == PInvokeGeneratorTransparentStructKind.TypedefHex); + + private bool IsUnchecked(string targetTypeName, Stmt stmt) + { + if (IsPrevContextDecl(out var parentVarDecl, out _)) + { + var cursorName = GetCursorName(parentVarDecl); + + if (cursorName.StartsWith("ClangSharpMacro_", StringComparison.Ordinal) && _config.WithTransparentStructs.TryGetValue(targetTypeName, out var transparentStruct)) + { + targetTypeName = transparentStruct.Name; + } + } + + switch (stmt.StmtClass) + { + // case CX_StmtClass_BinaryConditionalOperator: + + case CX_StmtClass_ConditionalOperator: + { + var conditionalOperator = (ConditionalOperator)stmt; + return IsUnchecked(targetTypeName, conditionalOperator.LHS) + || IsUnchecked(targetTypeName, conditionalOperator.RHS) + || IsUnchecked(targetTypeName, conditionalOperator.Handle.Evaluate); + } + + // case CX_StmtClass_AddrLabelExpr: + // case CX_StmtClass_ArrayInitIndexExpr: + // case CX_StmtClass_ArrayInitLoopExpr: + + case CX_StmtClass_ArraySubscriptExpr: + { + var arraySubscriptExpr = (ArraySubscriptExpr)stmt; + return IsUnchecked(targetTypeName, arraySubscriptExpr.LHS) + || IsUnchecked(targetTypeName, arraySubscriptExpr.RHS); + } + + // case CX_StmtClass_ArrayTypeTraitExpr: + // case CX_StmtClass_AsTypeExpr: + // case CX_StmtClass_AtomicExpr: + + case CX_StmtClass_BinaryOperator: + { + var binaryOperator = (BinaryOperator)stmt; + return IsUnchecked(targetTypeName, binaryOperator.LHS) + || IsUnchecked(targetTypeName, binaryOperator.RHS) + || IsUnchecked(targetTypeName, binaryOperator.Handle.Evaluate) + || IsOverflow(binaryOperator); + } + + // case CX_StmtClass_CompoundAssignOperator: + // case CX_StmtClass_BlockExpr: + // case CX_StmtClass_CXXBindTemporaryExpr: + + case CX_StmtClass_CXXBoolLiteralExpr: + { + return false; + } + + case CX_StmtClass_CXXConstructExpr: + { + return false; + } + + case CX_StmtClass_CXXTemporaryObjectExpr: + { + return false; + } + + case CX_StmtClass_CXXDefaultArgExpr: + { + return false; + } + + case CX_StmtClass_CXXDefaultInitExpr: + { + return false; + } + + // case CX_StmtClass_CXXDeleteExpr: + + case CX_StmtClass_CXXDependentScopeMemberExpr: + { + return false; + } + + // case CX_StmtClass_CXXFoldExpr: + // case CX_StmtClass_CXXInheritedCtorInitExpr: + + case CX_StmtClass_CXXNewExpr: + { + return false; + } + + // case CX_StmtClass_CXXNoexceptExpr: + + case CX_StmtClass_CXXNullPtrLiteralExpr: + { + return false; + } + + // case CX_StmtClass_CXXPseudoDestructorExpr: + // case CX_StmtClass_CXXRewrittenBinaryOperator: + // case CX_StmtClass_CXXScalarValueInitExpr: + // case CX_StmtClass_CXXStdInitializerListExpr: + + case CX_StmtClass_CXXThisExpr: + { + return false; + } + + // case CX_StmtClass_CXXThrowExpr: + // case CX_StmtClass_CXXTypeidExpr: + // case CX_StmtClass_CXXUnresolvedConstructExpr: + + case CX_StmtClass_CXXUuidofExpr: + { + return false; + } + + case CX_StmtClass_CallExpr: + { + return false; + } + + // case CX_StmtClass_CUDAKernelCallExpr: + + case CX_StmtClass_CXXMemberCallExpr: + { + return false; + } + + case CX_StmtClass_CXXOperatorCallExpr: + { + return false; + } + + // case CX_StmtClass_UserDefinedLiteral: + // case CX_StmtClass_BuiltinBitCastExpr: + + case CX_StmtClass_CStyleCastExpr: + case CX_StmtClass_CXXStaticCastExpr: + case CX_StmtClass_CXXFunctionalCastExpr: + { + var explicitCastExpr = (ExplicitCastExpr)stmt; + var explicitCastExprTypeName = GetRemappedTypeName(explicitCastExpr, context: null, explicitCastExpr.Type, out _); + + return IsUnchecked(targetTypeName, explicitCastExpr.SubExprAsWritten) + || IsUnchecked(targetTypeName, explicitCastExpr.Handle.Evaluate) + || (IsUnsigned(targetTypeName) != IsUnsigned(explicitCastExprTypeName)); + } + + case CX_StmtClass_CXXConstCastExpr: + case CX_StmtClass_CXXDynamicCastExpr: + case CX_StmtClass_CXXReinterpretCastExpr: + { + var namedCastExpr = (CXXNamedCastExpr)stmt; + + return IsUnchecked(targetTypeName, namedCastExpr.SubExprAsWritten) + || IsUnchecked(targetTypeName, namedCastExpr.Handle.Evaluate); + } + + // case CX_StmtClass_ObjCBridgedCastExpr: + + case CX_StmtClass_ImplicitCastExpr: + { + var implicitCastExpr = (ImplicitCastExpr)stmt; + + return IsUnchecked(targetTypeName, implicitCastExpr.SubExprAsWritten) + || IsUnchecked(targetTypeName, implicitCastExpr.Handle.Evaluate); + } + + case CX_StmtClass_CharacterLiteral: + { + return false; + } + + // case CX_StmtClass_ChooseExpr: + // case CX_StmtClass_CompoundLiteralExpr: + // case CX_StmtClass_ConceptSpecializationExpr: + // case CX_StmtClass_ConvertVectorExpr: + // case CX_StmtClass_CoawaitExpr: + // case CX_StmtClass_CoyieldExpr: + + case CX_StmtClass_DeclRefExpr: + { + var declRefExpr = (DeclRefExpr)stmt; + return (declRefExpr.Decl is VarDecl varDecl) && varDecl.HasInit && IsUnchecked(targetTypeName, varDecl.Init); + } + + // case CX_StmtClass_DependentCoawaitExpr: + // case CX_StmtClass_DependentScopeDeclRefExpr: + // case CX_StmtClass_DesignatedInitExpr: + // case CX_StmtClass_DesignatedInitUpdateExpr: + // case CX_StmtClass_ExpressionTraitExpr: + // case CX_StmtClass_ExtVectorElementExpr: + // case CX_StmtClass_FixedPointLiteral: + + case CX_StmtClass_FloatingLiteral: + { + return false; + } + + // case CX_StmtClass_ConstantExpr: + + case CX_StmtClass_ExprWithCleanups: + { + var exprWithCleanups = (ExprWithCleanups)stmt; + return IsUnchecked(targetTypeName, exprWithCleanups.SubExpr); + } + + // case CX_StmtClass_FunctionParmPackExpr: + // case CX_StmtClass_GNUNullExpr: + // case CX_StmtClass_GenericSelectionExpr: + // case CX_StmtClass_ImaginaryLiteral: + // case CX_StmtClass_ImplicitValueInitExpr: + + case CX_StmtClass_InitListExpr: + { + return false; + } + + case CX_StmtClass_IntegerLiteral: + { + var integerLiteral = (IntegerLiteral)stmt; + var signedValue = integerLiteral.Value; + return IsUnchecked(targetTypeName, signedValue, integerLiteral.IsNegative, isHex: integerLiteral.ValueString.StartsWith("0x", StringComparison.Ordinal)); + } + + case CX_StmtClass_LambdaExpr: + { + return false; + } + + // case CX_StmtClass_MSPropertyRefExpr: + // case CX_StmtClass_MSPropertySubscriptExpr: + + case CX_StmtClass_MaterializeTemporaryExpr: + { + return false; + } + + case CX_StmtClass_MemberExpr: + { + return false; + } + + // case CX_StmtClass_NoInitExpr: + // case CX_StmtClass_ArraySectionExpr: + // case CX_StmtClass_ObjCArrayLiteral: + // case CX_StmtClass_ObjCAvailabilityCheckExpr: + // case CX_StmtClass_ObjCBoolLiteralExpr: + // case CX_StmtClass_ObjCBoxedExpr: + // case CX_StmtClass_ObjCDictionaryLiteral: + // case CX_StmtClass_ObjCEncodeExpr: + // case CX_StmtClass_ObjCIndirectCopyRestoreExpr: + // case CX_StmtClass_ObjCIsaExpr: + // case CX_StmtClass_ObjCIvarRefExpr: + // case CX_StmtClass_ObjCMessageExpr: + // case CX_StmtClass_ObjCPropertyRefExpr: + // case CX_StmtClass_ObjCProtocolExpr: + // case CX_StmtClass_ObjCSelectorExpr: + // case CX_StmtClass_ObjCStringLiteral: + // case CX_StmtClass_ObjCSubscriptRefExpr: + + case CX_StmtClass_OffsetOfExpr: + { + return false; + } + + // case CX_StmtClass_OpaqueValueExpr: + // case CX_StmtClass_UnresolvedLookupExpr: + // case CX_StmtClass_UnresolvedMemberExpr: + // case CX_StmtClass_PackExpansionExpr: + + case CX_StmtClass_ParenExpr: + { + var parenExpr = (ParenExpr)stmt; + return IsUnchecked(targetTypeName, parenExpr.SubExpr) + || IsUnchecked(targetTypeName, parenExpr.Handle.Evaluate); + } + + case CX_StmtClass_ParenListExpr: + { + var parenListExpr = (ParenListExpr)stmt; + + foreach (var expr in parenListExpr.Exprs) + { + if (IsUnchecked(targetTypeName, expr) || IsUnchecked(targetTypeName, expr.Handle.Evaluate)) + { + return true; + } + } + + return false; + } + + // case CX_StmtClass_PredefinedExpr: + // case CX_StmtClass_PseudoObjectExpr: + // case CX_StmtClass_RequiresExpr: + // case CX_StmtClass_ShuffleVectorExpr: + // case CX_StmtClass_SizeOfPackExpr: + // case CX_StmtClass_SourceLocExpr: + // case CX_StmtClass_StmtExpr: + + case CX_StmtClass_StringLiteral: + { + return false; + } + + case CX_StmtClass_SubstNonTypeTemplateParmExpr: + { + return false; + } + + // case CX_StmtClass_SubstNonTypeTemplateParmPackExpr: + // case CX_StmtClass_TypeTraitExpr: + // case CX_StmtClass_TypoExpr: + + case CX_StmtClass_UnaryExprOrTypeTraitExpr: + { + var unaryExprOrTypeTraitExpr = (UnaryExprOrTypeTraitExpr)stmt; + + var argumentType = unaryExprOrTypeTraitExpr.TypeOfArgument; + + long alignment32 = -1; + long alignment64 = -1; + + GetTypeSize(unaryExprOrTypeTraitExpr, argumentType, ref alignment32, ref alignment64, out var size32, out var size64); + + switch (unaryExprOrTypeTraitExpr.Kind) + { + case CX_UETT_SizeOf: + { + switch (targetTypeName) + { + case "bool": + case "Boolean": + case "byte": + case "Byte": + case "char": + case "Char": + case "ushort": + case "UInt16": + case "uint": + case "UInt32": + case "nuint": + case "sbyte": + case "SByte": + case "short": + case "Int16": + { + return (size32 != size64) || !IsPrevContextDecl(out _, out _); + } + + case "ulong": + case "UInt64": + case "int": + case "Int32": + case "nint": + case "long": + case "Int64": + { + return false; + } + + default: + { + return false; + } + } + } + + default: + { + return false; + } + } + } + + case CX_StmtClass_UnaryOperator: + { + var unaryOperator = (UnaryOperator)stmt; + + if (IsUnchecked(targetTypeName, unaryOperator.SubExpr)) + { + return true; + } + + var evaluation = unaryOperator.Handle.Evaluate; + + if (IsUnchecked(targetTypeName, evaluation)) + { + return true; + } + + var sourceTypeName = GetTypeName(stmt, context: null, type: unaryOperator.SubExpr.Type, ignoreTransparentStructsWhereRequired: false, isTemplate: false, nativeTypeName: out _); + + switch (unaryOperator.Opcode) + { + case CXUnaryOperator_Minus: + { + return IsUnsigned(targetTypeName); + } + + case CXUnaryOperator_Not: + { + return IsUnsigned(targetTypeName) != IsUnsigned(sourceTypeName); + } + + default: + { + return false; + } + } + } + + // case CX_StmtClass_VAArgExpr: + + default: + { + AddDiagnostic(DiagnosticLevel.Warning, $"Unsupported statement class: '{stmt.StmtClassName}'. Generated bindings may not be unchecked.", stmt); + return false; + } + } + + bool IsOverflow(BinaryOperator binaryOperator) + { + var lhs = binaryOperator.LHS; + var rhs = binaryOperator.RHS; + + long lhsValue, rhsValue; + + if (IsStmtAsWritten(lhs, out var lhsIntegerLiteral, removeParens: true)) + { + lhsValue = lhsIntegerLiteral.Value; + } + else + { + var lhsEvaluation = lhs.Handle.Evaluate; + + if (lhsEvaluation.Kind == CXEval_Int) + { + lhsValue = lhsEvaluation.AsInt; + } + else + { + return false; + } + } + + if (IsStmtAsWritten(rhs, out var rhsIntegerLiteral, removeParens: true)) + { + rhsValue = rhsIntegerLiteral.Value; + } + else + { + var rhsEvaluation = rhs.Handle.Evaluate; + + if (rhsEvaluation.Kind == CXEval_Int) + { + rhsValue = rhsEvaluation.AsInt; + } + else + { + return false; + } + } + + var targetTypeName = GetRemappedTypeName(binaryOperator, context: null, binaryOperator.Type, out _, skipUsing: true); + var isUnsigned = IsUnsigned(targetTypeName); + + switch (binaryOperator.Opcode) + { + case CXBinaryOperator_Add: + { + return isUnsigned + ? (ulong)lhsValue + (ulong)rhsValue < (ulong)lhsValue + : lhsValue + rhsValue < lhsValue; + } + + case CXBinaryOperator_Sub: + { + return isUnsigned + ? (ulong)lhsValue - (ulong)rhsValue > (ulong)lhsValue + : lhsValue - rhsValue > lhsValue; + } + + default: + { + return false; + } + } + } + } + + private static bool IsUnchecked(string typeName, CXEvalResult evalResult) + { + if (evalResult.Kind != CXEval_Int) + { + return false; + } + + var signedValue = evalResult.AsLongLong; + return IsUnchecked(typeName, signedValue, signedValue < 0, isHex: false); + } + + private static bool IsUnchecked(string typeName, long signedValue, bool isNegative, bool isHex) + { + switch (typeName) + { + case "byte": + case "Byte": + { + var unsignedValue = unchecked((ulong)signedValue); + return unsignedValue is < byte.MinValue or > byte.MaxValue; + } + + case "char": + case "Char": + { + var unsignedValue = unchecked((ulong)signedValue); + return unsignedValue is < char.MinValue or > char.MaxValue; + } + + case "ushort": + case "UInt16": + { + var unsignedValue = unchecked((ulong)signedValue); + return unsignedValue is < ushort.MinValue or > ushort.MaxValue; + } + + case "uint": + case "UInt32": + case "nuint": + case "UIntPtr": + { + return false; + } + + case "ulong": + case "UInt64": + { + return false; + } + + case "sbyte": + case "SByte": + { + return (signedValue < sbyte.MinValue) || (sbyte.MaxValue < signedValue) || (isNegative && isHex); + } + + case "short": + case "Int16": + { + return (signedValue < short.MinValue) || (short.MaxValue < signedValue) || (isNegative && isHex); + } + + case "int": + case "Int32": + case "nint": + case "IntPtr": + { + return (signedValue < int.MinValue) || (int.MaxValue < signedValue) || (isNegative && isHex); + } + + case "long": + case "Int64": + { + return (signedValue < long.MinValue) || (long.MaxValue < signedValue) || (isNegative && isHex); + } + + default: + { + return false; + } + } + } + + private bool IsUnsafe(FieldDecl fieldDecl) + { + var type = fieldDecl.Type; + + if (IsType(fieldDecl, out _) && IsTypeConstantOrIncompleteArray(fieldDecl, type)) + { + var remappedName = GetRemappedTypeName(fieldDecl, context: null, type, out _, skipUsing: true, ignoreTransparentStructsWhereRequired: false); + return IsSupportedFixedSizedBufferType(remappedName); + } + + return IsUnsafe(fieldDecl, type); + } + + private bool IsUnsafe(FunctionDecl functionDecl) + { + var name = GetRemappedCursorName(functionDecl); + + if (_config.WithManualImports.Contains(name)) + { + return true; + } + + if (IsUnsafe(functionDecl, functionDecl.ReturnType)) + { + return true; + } + + foreach (var parmVarDecl in functionDecl.Parameters) + { + if (IsUnsafe(parmVarDecl)) + { + return true; + } + } + + return false; + } + + private bool IsUnsafe(ParmVarDecl parmVarDecl) + { + var type = parmVarDecl.Type; + return IsUnsafe(parmVarDecl, type); + } + + private bool IsUnsafe(RecordDecl recordDecl) + { + foreach (var decl in recordDecl.Decls) + { + if ((decl is FieldDecl fieldDecl) && IsUnsafe(fieldDecl)) + { + return true; + } + else if ((decl is RecordDecl nestedRecordDecl) && nestedRecordDecl.IsAnonymousStructOrUnion && (IsUnsafe(nestedRecordDecl) || Config.GenerateCompatibleCode)) + { + return true; + } + } + return (recordDecl is CXXRecordDecl cxxRecordDecl) && (HasVtbl(cxxRecordDecl, out var hasBaseVtbl) || hasBaseVtbl || HasUnsafeMethod(cxxRecordDecl)); + } + + private bool IsUnsafe(TypedefDecl typedefDecl, FunctionProtoType functionProtoType) + { + var returnType = functionProtoType.ReturnType; + + if (IsUnsafe(typedefDecl, returnType)) + { + return true; + } + + foreach (var paramType in functionProtoType.ParamTypes) + { + if (IsUnsafe(typedefDecl, paramType)) + { + return true; + } + } + + return false; + } + + private bool IsUnsafe(NamedDecl namedDecl, Type type) + { + var remappedName = GetRemappedTypeName(namedDecl, context: null, type, out _, skipUsing: true, ignoreTransparentStructsWhereRequired: false); + return remappedName.Contains('*', StringComparison.Ordinal); + } + + private static bool IsUnsigned(string typeName) + { + switch (typeName) + { + case "byte": + case "Byte": + case "char": + case "Char": + case "nuint": + case "UInt16": + case "uint": + case "UInt32": + case "ulong": + case "UInt64": + case "UIntPtr": + case "ushort": + case var _ when typeName.EndsWith('*'): + { + return true; + } + + case "Int16": + case "int": + case "Int32": + case "long": + case "Int64": + case "nint": + case "sbyte": + case "SByte": + case "short": + { + return false; + } + + default: + { + return false; + } + } + } +} diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.TypeResolution.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.TypeResolution.cs new file mode 100644 index 00000000..6a74b3d8 --- /dev/null +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.TypeResolution.cs @@ -0,0 +1,1186 @@ +// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; +using System.Text.RegularExpressions; +using ClangSharp.Abstractions; +using ClangSharp.CSharp; +using ClangSharp.Interop; +using ClangSharp.XML; +using static ClangSharp.Interop.CX_AttrKind; +using static ClangSharp.Interop.CX_CXXAccessSpecifier; +using static ClangSharp.Interop.CX_StmtClass; +using static ClangSharp.Interop.CX_UnaryExprOrTypeTrait; +using static ClangSharp.Interop.CXBinaryOperatorKind; +using static ClangSharp.Interop.CXCallingConv; +using static ClangSharp.Interop.CXDiagnosticSeverity; +using static ClangSharp.Interop.CXEvalResultKind; +using static ClangSharp.Interop.CXTemplateArgumentKind; +using static ClangSharp.Interop.CXTranslationUnit_Flags; +using static ClangSharp.Interop.CXTypeKind; +using static ClangSharp.Interop.CXUnaryOperatorKind; + +namespace ClangSharp; + +public sealed partial class PInvokeGenerator +{ + private string GetTargetTypeName(Cursor cursor, out string nativeTypeName) + { + var targetTypeName = ""; + nativeTypeName = ""; + + if (cursor is Decl decl) + { + if (decl is EnumConstantDecl enumConstantDecl) + { + targetTypeName = enumConstantDecl.DeclContext is EnumDecl enumDecl + ? GetRemappedTypeName(enumDecl, context: null, enumDecl.IntegerType, out nativeTypeName) + : GetRemappedTypeName(enumConstantDecl, context: null, enumConstantDecl.Type, out nativeTypeName); + } + else if (decl is TypeDecl previousTypeDecl) + { + targetTypeName = GetRemappedTypeName(previousTypeDecl, context: null, previousTypeDecl.TypeForDecl, out nativeTypeName); + } + else if (decl is VarDecl varDecl) + { + if (varDecl is ParmVarDecl parmVarDecl) + { + targetTypeName = GetRemappedTypeName(parmVarDecl, context: null, parmVarDecl.Type, out nativeTypeName); + + if (!_config.GenerateDisableRuntimeMarshalling && (parmVarDecl.ParentFunctionOrMethod is FunctionDecl functionDecl) && (((functionDecl is CXXMethodDecl cxxMethodDecl) && cxxMethodDecl.IsVirtual) || (functionDecl.Body is null)) && targetTypeName.Equals("bool", StringComparison.Ordinal)) + { + // bool is not blittable when DisableRuntimeMarshalling is not specified, so we shouldn't use it for P/Invoke signatures + targetTypeName = "byte"; + nativeTypeName = string.IsNullOrWhiteSpace(nativeTypeName) ? "bool" : nativeTypeName; + } + } + else + { + var type = varDecl.Type; + var cursorName = GetCursorName(varDecl).AsSpan(); + + if (cursorName.StartsWith("ClangSharpMacro_", StringComparison.Ordinal)) + { + cursorName = cursorName["ClangSharpMacro_".Length..]; + + if (_config._withTypes.GetAlternateLookup>().TryGetValue(cursorName, out targetTypeName)) + { + return targetTypeName; + } + + type = varDecl.Init.Type; + } + + targetTypeName = GetRemappedTypeName(varDecl, context: null, type, out nativeTypeName); + } + } + + } + else if ((cursor is Expr expr) && (expr is not MemberExpr)) + { + targetTypeName = GetRemappedTypeName(expr, context: null, expr.Type, out nativeTypeName); + } + + return targetTypeName; + } + + private string GetTypeName(Cursor? cursor, Cursor? context, Type type, bool ignoreTransparentStructsWhereRequired, bool isTemplate, out string nativeTypeName) + { + if (_typeNames.TryGetValue((cursor, context, type), out var result)) + { + nativeTypeName = result.nativeTypeName; + return result.typeName; + } + else if (IsType(cursor, type, out var tagType) && tagType.Decl.Handle.IsAnonymous) + { + // In order to avoid minor path differences, casing, and other deltas across different + // invocations of the tool, we want to use the "built" anonymous name so we get a more + // minimal but still accurate set of information embedded in the output. + + result.typeName = GetAnonymousName(tagType.Decl, tagType.KindSpelling); + result.nativeTypeName = result.typeName; + + _typeNames[(cursor, context, type)] = result; + + nativeTypeName = result.nativeTypeName; + return result.typeName; + } + else + { + return GetTypeName(cursor, context, type, type, ignoreTransparentStructsWhereRequired, isTemplate, out nativeTypeName); + } + } + + private string GetTypeName(Cursor? cursor, Cursor? context, Type rootType, Type type, bool ignoreTransparentStructsWhereRequired, bool isTemplate, out string nativeTypeName) + { + if (!_typeNames.TryGetValue((cursor, context, type), out var result)) + { + result.typeName = type.AsString.NormalizePath() + .Replace("unnamed enum at", "anonymous enum at", StringComparison.Ordinal) + .Replace("unnamed struct at", "anonymous struct at", StringComparison.Ordinal) + .Replace("unnamed union at", "anonymous union at", StringComparison.Ordinal); + + result.nativeTypeName = result.typeName; + + // We don't want to handle these using IsType because we need to specially + // handle cases like TypedefType at each level of the type hierarchy + + if (type is ArrayType arrayType) + { + result.typeName = GetRemappedTypeName(cursor, context, arrayType.ElementType, out _, skipUsing: true, ignoreTransparentStructsWhereRequired); + + if (cursor is FunctionDecl or ParmVarDecl) + { + result.typeName += '*'; + } + } + else if (type is AttributedType attributedType) + { + result.typeName = GetTypeName(cursor, context, rootType, attributedType.ModifiedType, ignoreTransparentStructsWhereRequired, isTemplate, out _); + } + else if (type is BuiltinType) + { + switch (type.Kind) + { + case CXType_Void: + { + result.typeName = (cursor is null) ? "Void" : "void"; + break; + } + + case CXType_Bool: + { + result.typeName = (cursor is null) ? "Boolean" : "bool"; + break; + } + + case CXType_Char_U: + case CXType_UChar: + { + result.typeName = (cursor is null) ? "Byte" : "byte"; + break; + } + + case CXType_Char16: + { + if (_config.GenerateDisableRuntimeMarshalling) + { + result.typeName = (cursor is null) ? "Char" : "char"; + break; + } + goto case CXType_UShort; + } + + case CXType_UShort: + { + result.typeName = (cursor is null) ? "UInt16" : "ushort"; + break; + } + + case CXType_UInt: + { + result.typeName = (cursor is null) ? "UInt32" : "uint"; + break; + } + + case CXType_ULong: + { + if (_config.GenerateUnixTypes) + { + result.typeName = _config.ExcludeNIntCodegen ? "UIntPtr" : "nuint"; + } + else + { + goto case CXType_UInt; + } + break; + } + + case CXType_ULongLong: + { + result.typeName = (cursor is null) ? "UInt64" : "ulong"; + break; + } + + case CXType_Char_S: + case CXType_SChar: + { + result.typeName = (cursor is null) ? "SByte" : "sbyte"; + break; + } + + case CXType_WChar: + { + if (_config.GenerateUnixTypes) + { + goto case CXType_UInt; + } + else + { + goto case CXType_Char16; + } + } + + case CXType_Short: + { + result.typeName = (cursor is null) ? "Int16" : "short"; + break; + } + + case CXType_Int: + { + result.typeName = (cursor is null) ? "Int32" : "int"; + break; + } + + case CXType_Long: + { + if (_config.GenerateUnixTypes) + { + result.typeName = _config.ExcludeNIntCodegen ? "IntPtr" : "nint"; + } + else + { + goto case CXType_Int; + } + break; + } + + case CXType_LongLong: + { + result.typeName = (cursor is null) ? "Int64" : "long"; + break; + } + + case CXType_Float: + { + result.typeName = (cursor is null) ? "Single" : "float"; + break; + } + + case CXType_Double: + { + result.typeName = (cursor is null) ? "Double" : "double"; + break; + } + + case CXType_NullPtr: + { + result.typeName = "null"; + break; + } + + default: + { + AddDiagnostic(DiagnosticLevel.Warning, $"Unsupported builtin type: '{type.KindSpelling}'. Falling back '{result.typeName}'.", cursor); + break; + } + } + } + else if (type is DecltypeType decltypeType) + { + result.typeName = GetTypeName(cursor, context, rootType, decltypeType.UnderlyingType, ignoreTransparentStructsWhereRequired, isTemplate, out _); + } + else if (type is DeducedType deducedType) + { + result.typeName = GetTypeName(cursor, context, rootType, deducedType.GetDeducedType, ignoreTransparentStructsWhereRequired, isTemplate, out _); + } + else if (type is DependentNameType dependentNameType) + { + if (dependentNameType.IsSugared) + { + result.typeName = GetTypeName(cursor, context, rootType, dependentNameType.Desugar, ignoreTransparentStructsWhereRequired, isTemplate, out _); + } + else + { + // The default name should be correct + } + } + else if (type is ElaboratedType elaboratedType) + { + result.typeName = GetTypeName(cursor, context, rootType, elaboratedType.NamedType, ignoreTransparentStructsWhereRequired, isTemplate, out var nativeNamedTypeName); + + if (!string.IsNullOrWhiteSpace(nativeNamedTypeName) && + !result.nativeTypeName.StartsWith("const ", StringComparison.Ordinal) && + !result.nativeTypeName.StartsWith("enum ", StringComparison.Ordinal) && + !result.nativeTypeName.StartsWith("struct ", StringComparison.Ordinal) && + !result.nativeTypeName.StartsWith("union ", StringComparison.Ordinal)) + { + result.nativeTypeName = nativeNamedTypeName; + } + } + else if (type is FunctionType functionType) + { + result.typeName = GetTypeNameForPointeeType(cursor, context, rootType, functionType, ignoreTransparentStructsWhereRequired, isTemplate, out _, out _); + } + else if (type is InjectedClassNameType injectedClassNameType) + { + result.typeName = GetTypeName(cursor, context, rootType, injectedClassNameType.InjectedTST, ignoreTransparentStructsWhereRequired, isTemplate, out _); + } + else if (type is PackExpansionType packExpansionType) + { + result.typeName = GetTypeName(cursor, context, rootType, packExpansionType.Pattern, ignoreTransparentStructsWhereRequired, isTemplate, out _); + } + else if (type is PointerType pointerType) + { + result.typeName = GetTypeNameForPointeeType(cursor, context, rootType, pointerType.PointeeType, ignoreTransparentStructsWhereRequired, isTemplate, out var nativePointeeTypeName, out var isAdjusted); + + if (isAdjusted) + { + result.nativeTypeName = $"{nativePointeeTypeName} *"; + } + } + else if (type is ReferenceType referenceType) + { + result.typeName = GetTypeNameForPointeeType(cursor, context, rootType, referenceType.PointeeType, ignoreTransparentStructsWhereRequired, isTemplate, out var nativePointeeTypeName, out var isAdjusted); + + if (isAdjusted) + { + result.nativeTypeName = $"{nativePointeeTypeName} &"; + } + } + else if (type is SubstTemplateTypeParmType substTemplateTypeParmType) + { + result.typeName = GetTypeName(cursor, context, rootType, substTemplateTypeParmType.ReplacementType, ignoreTransparentStructsWhereRequired, isTemplate, out _); + } + else if (type is TagType tagType) + { + if (tagType.Decl.Handle.IsAnonymous) + { + // In order to avoid minor path differences, casing, and other deltas across different + // invocations of the tool, we want to use the "built" anonymous name so we get a more + // minimal but still accurate set of information embedded in the output. + + result.typeName = GetAnonymousName(tagType.Decl, tagType.KindSpelling); + result.nativeTypeName = result.typeName; + } + else if (tagType.Handle.IsConstQualified) + { + result.typeName = GetTypeName(cursor, context, rootType, tagType.Decl.TypeForDecl, ignoreTransparentStructsWhereRequired, isTemplate, out _); + } + else + { + // The default name should be correct for C++, but C may have a prefix we need to strip + + if (result.typeName.StartsWith("enum ", StringComparison.Ordinal)) + { + result.typeName = result.typeName[5..]; + } + else if (result.typeName.StartsWith("struct ", StringComparison.Ordinal)) + { + result.typeName = result.typeName[7..]; + } + else if (result.typeName.StartsWith("union ", StringComparison.Ordinal)) + { + result.typeName = result.typeName[6..]; + } + } + + if (result.typeName.Contains("::", StringComparison.Ordinal)) + { + result.typeName = result.typeName.Split(s_doubleColonSeparator, StringSplitOptions.RemoveEmptyEntries).Last(); + result.typeName = GetRemappedName(result.typeName, cursor, tryRemapOperatorName: false, out _, skipUsing: true); + } + } + else if (type is TemplateSpecializationType templateSpecializationType) + { + var nameBuilder = new StringBuilder(); + + var templateTypeDecl = IsType(cursor, templateSpecializationType, out var recordType) + ? recordType.Decl + : (NamedDecl)templateSpecializationType.TemplateName.AsTemplateDecl; + + var templateTypeDeclName = GetRemappedCursorName(templateTypeDecl, out _, skipUsing: true); + var isStdAtomic = false; + + if (templateTypeDeclName.Equals("atomic", StringComparison.Ordinal)) + { + isStdAtomic = (templateTypeDecl.Parent is NamespaceDecl namespaceDecl) && namespaceDecl.IsStdNamespace; + } + + if (!isStdAtomic) + { + _ = nameBuilder.Append(templateTypeDeclName); + _ = nameBuilder.Append('<'); + } + else + { + _ = nameBuilder.Append("volatile "); + } + + var shouldWritePrecedingComma = false; + + foreach (var arg in templateSpecializationType.Args) + { + if (shouldWritePrecedingComma) + { + _ = nameBuilder.Append(','); + _ = nameBuilder.Append(' '); + } + + var typeName = ""; + + switch (arg.Kind) + { + case CXTemplateArgumentKind_Type: + { + typeName = GetRemappedTypeName(cursor, context: null, arg.AsType, out var nativeAsTypeName, skipUsing: true, isTemplate: true); + break; + } + + case CXTemplateArgumentKind_Expression: + { + var oldOutputBuilder = _outputBuilder; + _outputBuilder = new CSharpOutputBuilder("ClangSharp_TemplateSpecializationType_AsExpr", this); + + Visit(arg.AsExpr); + typeName = _outputBuilder.ToString() ?? ""; + + _outputBuilder = oldOutputBuilder; + break; + } + + default: + { + typeName = result.typeName; + AddDiagnostic(DiagnosticLevel.Warning, $"Unsupported template argument kind: '{arg.Kind}'. Falling back '{result.typeName}'.", cursor); + break; + } + } + + if (!_config.GenerateDisableRuntimeMarshalling && typeName.Equals("bool", StringComparison.Ordinal)) + { + // bool is not blittable when DisableRuntimeMarshalling is not specified, so we shouldn't use it for P/Invoke signatures + typeName = "byte"; + } + + if (typeName.EndsWith('*') || typeName.Contains("delegate*", StringComparison.Ordinal)) + { + if (Config.GenerateGenericPointerWrapper) + { + AddDiagnostic(DiagnosticLevel.Warning, $"Unhandled pointer in template: '{typeName}'. Falling back 'IntPtr'.", cursor); + } + + // Pointers are not yet supported as generic arguments; remap to IntPtr + typeName = "IntPtr"; + _outputBuilder?.EmitSystemSupport(); + } + + _ = nameBuilder.Append(typeName); + + shouldWritePrecedingComma = true; + } + + if (!isStdAtomic) + { + _ = nameBuilder.Append('>'); + } + + result.typeName = nameBuilder.ToString(); + } + else if (type is TemplateTypeParmType templateTypeParmType) + { + if (templateTypeParmType.IsSugared) + { + result.typeName = GetTypeName(cursor, context, rootType, templateTypeParmType.Desugar, ignoreTransparentStructsWhereRequired, isTemplate, out _); + } + else + { + // The default name should be correct + } + } + else if (type is TypedefType typedefType) + { + // We check remapped names here so that types that have variable sizes + // can be treated correctly. Otherwise, they will resolve to a particular + // platform size, based on whatever parameters were passed into clang. + + var remappedName = GetRemappedName(result.typeName, cursor, tryRemapOperatorName: false, out var wasRemapped, skipUsing: true); + result.typeName = wasRemapped ? remappedName : GetTypeName(cursor, context, rootType, typedefType.Decl.UnderlyingType, ignoreTransparentStructsWhereRequired, isTemplate, out _); + } + else if (type is UsingType usingType) + { + result.typeName = GetTypeName(cursor, context, rootType, usingType.Desugar, ignoreTransparentStructsWhereRequired, isTemplate, out _); + } + else + { + AddDiagnostic(DiagnosticLevel.Warning, $"Unsupported type: '{type.TypeClass}'. Falling back '{result.typeName}'.", cursor); + } + + Debug.Assert(!string.IsNullOrWhiteSpace(result.typeName)); + Debug.Assert(!string.IsNullOrWhiteSpace(result.nativeTypeName)); + + if (IsNativeTypeNameEquivalent(result.nativeTypeName, result.typeName)) + { + result.nativeTypeName = string.Empty; + } + + _typeNames[(cursor, context, type)] = result; + } + + nativeTypeName = result.nativeTypeName; + return result.typeName; + } + + private string GetTypeNameForPointeeType(Cursor? cursor, Cursor? context, Type rootType, Type pointeeType, bool ignoreTransparentStructsWhereRequired, bool isTemplate, out string nativePointeeTypeName, out bool isAdjusted) + { + var name = pointeeType.AsString; + + nativePointeeTypeName = name; + isAdjusted = false; + + // We don't want to handle these using IsType because we need to specially + // handle cases like TypedefType at each level of the type hierarchy + + if (pointeeType is AttributedType attributedType) + { + name = GetTypeNameForPointeeType(cursor, context, rootType, attributedType.ModifiedType, ignoreTransparentStructsWhereRequired, isTemplate, out var nativeModifiedTypeName, out isAdjusted); + } + else if (pointeeType is ElaboratedType elaboratedType) + { + name = GetTypeNameForPointeeType(cursor, context, rootType, elaboratedType.NamedType, ignoreTransparentStructsWhereRequired, isTemplate, out var nativeNamedTypeName, out isAdjusted); + + if (!string.IsNullOrWhiteSpace(nativeNamedTypeName) && + !nativePointeeTypeName.StartsWith("const ", StringComparison.Ordinal) && + !nativePointeeTypeName.StartsWith("enum ", StringComparison.Ordinal) && + !nativePointeeTypeName.StartsWith("struct ", StringComparison.Ordinal) && + !nativePointeeTypeName.StartsWith("union ", StringComparison.Ordinal)) + { + nativePointeeTypeName = nativeNamedTypeName; + isAdjusted = true; + } + } + else if (pointeeType is FunctionType functionType) + { + if (!_config.ExcludeFnptrCodegen && IsType(cursor, functionType, out var functionProtoType)) + { + _config.ExcludeFnptrCodegen = true; + var callConv = GetCallingConvention(cursor, context, rootType); + _config.ExcludeFnptrCodegen = false; + + var needsReturnFixup = false; + var returnTypeName = GetRemappedTypeName(cursor, context: null, functionType.ReturnType, out _, skipUsing: true); + + if (!_config.GenerateDisableRuntimeMarshalling && returnTypeName.Equals("bool", StringComparison.Ordinal)) + { + // bool is not blittable when DisableRuntimeMarshalling is not specified, so we shouldn't use it for P/Invoke signatures + returnTypeName = "byte"; + } + + var nameBuilder = new StringBuilder(); + _ = nameBuilder.Append("delegate"); + _ = nameBuilder.Append('*'); + + var isMacroDefinitionRecord = (cursor is VarDecl varDecl) && GetCursorName(varDecl).StartsWith("ClangSharpMacro_", StringComparison.Ordinal); + + if (!isMacroDefinitionRecord) + { + _ = nameBuilder.Append(" unmanaged"); + var hasSuppressGCTransition = HasSuppressGCTransition(cursor); + + if (callConv != CallConv.Winapi) + { + _ = nameBuilder.Append('['); + _ = nameBuilder.Append(callConv.AsString(true)); + + if (hasSuppressGCTransition) + { + _ = nameBuilder.Append(", SuppressGCTransition"); + } + _ = nameBuilder.Append(']'); + } + else if (hasSuppressGCTransition) + { + _ = nameBuilder.Append("[SuppressGCTransition]"); + } + } + + _ = nameBuilder.Append('<'); + + if ((cursor is CXXMethodDecl cxxMethodDecl) && (context is CXXRecordDecl cxxRecordDecl)) + { + var cxxRecordDeclName = GetRemappedCursorName(cxxRecordDecl); + needsReturnFixup = cxxMethodDecl.IsVirtual && NeedsReturnFixup(cxxMethodDecl); + + _ = nameBuilder.Append(EscapeName(cxxRecordDeclName)); + _ = nameBuilder.Append('*'); + _ = nameBuilder.Append(','); + _ = nameBuilder.Append(' '); + + if (needsReturnFixup) + { + _ = nameBuilder.Append(returnTypeName); + _ = nameBuilder.Append('*'); + _ = nameBuilder.Append(','); + _ = nameBuilder.Append(' '); + } + } + + IEnumerable paramTypes = functionProtoType.ParamTypes; + + if (isMacroDefinitionRecord) + { + Debug.Assert(cursor is not null); + varDecl = (VarDecl)cursor; + + if (IsStmtAsWritten(varDecl.Init, out var declRefExpr, removeParens: true) && (declRefExpr.Decl is FunctionDecl functionDecl)) + { + cursor = functionDecl; + paramTypes = functionDecl.Parameters.Select((param) => param.Type); + returnTypeName = GetRemappedTypeName(cursor, context: null, functionDecl.ReturnType, out _, skipUsing: true); + } + } + + foreach (var paramType in paramTypes) + { + var typeName = GetRemappedTypeName(cursor, context: null, paramType, out _, skipUsing: true); + + if (!_config.GenerateDisableRuntimeMarshalling && typeName.Equals("bool", StringComparison.Ordinal)) + { + // bool is not blittable when DisableRuntimeMarshalling is not specified, so we shouldn't use it for P/Invoke signatures + typeName = "byte"; + } + + _ = nameBuilder.Append(typeName); + _ = nameBuilder.Append(','); + _ = nameBuilder.Append(' '); + } + + if (!needsReturnFixup && ignoreTransparentStructsWhereRequired && _config.WithTransparentStructs.TryGetValue(returnTypeName, out var transparentStruct)) + { + _ = nameBuilder.Append(transparentStruct.Name); + } + else + { + _ = nameBuilder.Append(returnTypeName); + + if (needsReturnFixup) + { + _ = nameBuilder.Append('*'); + } + } + + _ = nameBuilder.Append('>'); + name = nameBuilder.ToString(); + } + else + { + name = "IntPtr"; + } + } + else if (pointeeType is TypedefType typedefType) + { + // We check remapped names here so that types that have variable sizes + // can be treated correctly. Otherwise, they will resolve to a particular + // platform size, based on whatever parameters were passed into clang. + + var remappedName = GetRemappedName(name, cursor, tryRemapOperatorName: false, out var wasRemapped, skipUsing: true); + + if (wasRemapped) + { + name = isTemplate && Config.GenerateGenericPointerWrapper + ? $"Pointer<{remappedName}>" + : $"{remappedName}*"; + } + else + { + name = GetTypeNameForPointeeType(cursor, context, rootType, typedefType.Decl.UnderlyingType, ignoreTransparentStructsWhereRequired, isTemplate, out var nativeUnderlyingTypeName, out isAdjusted); + } + } + else + { + // Otherwise fields that point at anonymous structs get the wrong name + var remappedName = GetRemappedTypeName(cursor, context, pointeeType, out nativePointeeTypeName, skipUsing: true); + + name = isTemplate && Config.GenerateGenericPointerWrapper + ? $"Pointer<{remappedName}>" + : $"{remappedName}*"; + } + + return name; + } + + private void GetTypeSize(Cursor cursor, Type type, ref long alignment32, ref long alignment64, out long size32, out long size64) + { + var has8BytePrimitiveField = false; + GetTypeSize(cursor, type, ref alignment32, ref alignment64, ref has8BytePrimitiveField, out size32, out size64); + } + + private void GetTypeSize(Cursor cursor, Type type, ref long alignment32, ref long alignment64, ref bool has8BytePrimitiveField, out long size32, out long size64) + { + size32 = 0; + size64 = 0; + + // We don't want to handle these using IsType because we need to specially + // handle cases like TypedefType at each level of the type hierarchy + + if (type is ArrayType arrayType) + { + if (IsTypeConstantOrIncompleteArray(cursor, type)) + { + var count = Math.Max((arrayType as ConstantArrayType)?.Size ?? 0, 1); + GetTypeSize(cursor, arrayType.ElementType, ref alignment32, ref alignment64, ref has8BytePrimitiveField, out var elementSize32, out var elementSize64); + + size32 = elementSize32 * Math.Max(count, 1); + size64 = elementSize64 * Math.Max(count, 1); + + if (alignment32 == -1) + { + alignment32 = elementSize32; + } + + if (alignment64 == -1) + { + alignment64 = elementSize64; + } + } + else + { + size32 = 4; + size64 = 8; + + if (alignment32 == -1) + { + alignment32 = 4; + } + + if (alignment64 == -1) + { + alignment64 = 8; + } + } + } + else if (type is AttributedType attributedType) + { + GetTypeSize(cursor, attributedType.ModifiedType, ref alignment32, ref alignment64, ref has8BytePrimitiveField, out size32, out size64); + } + else if (type is BuiltinType) + { + switch (type.Kind) + { + case CXType_Bool: + case CXType_Char_U: + case CXType_UChar: + case CXType_Char_S: + case CXType_SChar: + { + size32 = 1; + size64 = 1; + break; + } + + case CXType_UShort: + case CXType_Short: + { + size32 = 2; + size64 = 2; + break; + } + + case CXType_UInt: + case CXType_Int: + case CXType_Float: + { + size32 = 4; + size64 = 4; + break; + } + + case CXType_ULong: + case CXType_Long: + { + if (_config.GenerateUnixTypes) + { + size32 = 4; + size64 = 8; + + if (alignment32 == -1) + { + alignment32 = 4; + } + + if (alignment64 == -1) + { + alignment64 = 8; + } + } + else + { + goto case CXType_UInt; + } + break; + } + + case CXType_ULongLong: + case CXType_LongLong: + case CXType_Double: + { + size32 = 8; + size64 = 8; + + if (alignment32 == -1) + { + alignment32 = 8; + } + + if (alignment64 == -1) + { + alignment64 = 8; + } + + has8BytePrimitiveField = true; + break; + } + + case CXType_WChar: + { + if (_config.GenerateUnixTypes) + { + goto case CXType_Int; + } + else + { + goto case CXType_UShort; + } + } + + default: + { + AddDiagnostic(DiagnosticLevel.Error, $"Unsupported builtin type: '{type.KindSpelling}.", cursor); + break; + } + } + } + else if (type is DecltypeType decltypeType) + { + GetTypeSize(cursor, decltypeType.UnderlyingType, ref alignment32, ref alignment64, ref has8BytePrimitiveField, out size32, out size64); + } + else if (type is ElaboratedType elaboratedType) + { + GetTypeSize(cursor, elaboratedType.NamedType, ref alignment32, ref alignment64, ref has8BytePrimitiveField, out size32, out size64); + } + else if (type is EnumType enumType) + { + GetTypeSize(cursor, enumType.Decl.IntegerType, ref alignment32, ref alignment64, ref has8BytePrimitiveField, out size32, out size64); + } + else if (type is FunctionType or PointerType or ReferenceType) + { + size32 = 4; + size64 = 8; + + if (alignment32 == -1) + { + alignment32 = 4; + } + + if (alignment64 == -1) + { + alignment64 = 8; + } + } + else if (type is InjectedClassNameType) + { + // Nothing to handle + } + else if (type is RecordType recordType) + { + var recordTypeAlignOf = Math.Min(recordType.Handle.AlignOf, 8); + + if (alignment32 == -1) + { + alignment32 = recordTypeAlignOf; + } + + if (alignment64 == -1) + { + alignment64 = recordTypeAlignOf; + } + + long maxFieldAlignment32 = -1; + long maxFieldAlignment64 = -1; + + long maxFieldSize32 = 0; + long maxFieldSize64 = 0; + + var anyFieldIs8BytePrimitive = false; + + if (recordType.Decl is CXXRecordDecl cxxRecordDecl) + { + if (HasVtbl(cxxRecordDecl, out _)) + { + size32 += 4; + size64 += 8; + + if (alignment32 < 4) + { + alignment32 = Math.Max(Math.Min(alignment32, 4), 1); + } + + if (alignment64 < 4) + { + alignment64 = Math.Max(Math.Min(alignment32, 8), 1); + } + + maxFieldSize32 = Math.Max(maxFieldSize32, 4); + maxFieldSize64 = Math.Max(maxFieldSize64, 8); + + maxFieldAlignment32 = Math.Max(maxFieldSize32, 4); + maxFieldAlignment64 = Math.Max(maxFieldSize64, 8); + } + else + { + foreach (var baseCXXRecordDecl in cxxRecordDecl.Bases) + { + long fieldAlignment32 = -1; + long fieldAlignment64 = -1; + + GetTypeSize(baseCXXRecordDecl, baseCXXRecordDecl.Type, ref fieldAlignment32, ref fieldAlignment64, ref anyFieldIs8BytePrimitive, out var fieldSize32, out var fieldSize64); + + if ((fieldAlignment32 == -1) || (alignment32 < 4)) + { + fieldAlignment32 = Math.Max(Math.Min(alignment32, fieldSize32), 1); + } + + if ((fieldAlignment64 == -1) || (alignment64 < 4)) + { + fieldAlignment64 = Math.Max(Math.Min(alignment64, fieldSize64), 1); + } + + if ((size32 % fieldAlignment32) != 0) + { + size32 += fieldAlignment32 - (size32 % fieldAlignment32); + } + + if ((size64 % fieldAlignment64) != 0) + { + size64 += fieldAlignment64 - (size64 % fieldAlignment64); + } + + size32 += fieldSize32; + size64 += fieldSize64; + + maxFieldAlignment32 = Math.Max(maxFieldAlignment32, fieldAlignment32); + maxFieldAlignment64 = Math.Max(maxFieldAlignment64, fieldAlignment64); + + maxFieldSize32 = Math.Max(maxFieldSize32, fieldSize32); + maxFieldSize64 = Math.Max(maxFieldSize64, fieldSize64); + } + } + } + + var bitfieldPreviousSize32 = 0L; + var bitfieldPreviousSize64 = 0L; + var bitfieldRemainingBits32 = 0L; + var bitfieldRemainingBits64 = 0L; + + foreach (var fieldDecl in recordType.Decl.Fields) + { + long fieldAlignment32 = -1; + long fieldAlignment64 = -1; + + GetTypeSize(fieldDecl, fieldDecl.Type, ref fieldAlignment32, ref fieldAlignment64, ref anyFieldIs8BytePrimitive, out var fieldSize32, out var fieldSize64); + + var ignoreFieldSize32 = false; + var ignoreFieldSize64 = false; + + if (fieldDecl.IsBitField) + { + if (fieldSize32 != bitfieldPreviousSize32) + { + bitfieldRemainingBits32 = fieldSize32 * 8; + bitfieldPreviousSize32 = fieldSize32; + bitfieldRemainingBits32 -= fieldDecl.BitWidthValue; + } + else if (fieldDecl.BitWidthValue > bitfieldRemainingBits32) + { + if (bitfieldRemainingBits32 != bitfieldRemainingBits64) + { + ignoreFieldSize32 = true; + } + + bitfieldRemainingBits32 = fieldSize32 * 8; + bitfieldPreviousSize32 = fieldSize32; + bitfieldRemainingBits32 -= fieldDecl.BitWidthValue; + } + else + { + bitfieldPreviousSize32 = fieldSize32; + bitfieldRemainingBits32 -= fieldDecl.BitWidthValue; + ignoreFieldSize32 = true; + } + + if ((fieldSize64 != bitfieldPreviousSize64) || (fieldDecl.BitWidthValue > bitfieldRemainingBits64)) + { + bitfieldRemainingBits64 = fieldSize64 * 8; + bitfieldPreviousSize64 = fieldSize64; + bitfieldRemainingBits64 -= fieldDecl.BitWidthValue; + } + else + { + bitfieldPreviousSize64 = fieldSize64; + bitfieldRemainingBits64 -= fieldDecl.BitWidthValue; + ignoreFieldSize64 = true; + } + } + + if (!ignoreFieldSize32) + { + if ((fieldAlignment32 == -1) || (alignment32 < 4)) + { + fieldAlignment32 = Math.Max(Math.Min(alignment32, fieldSize32), 1); + } + + if ((size32 % fieldAlignment32) != 0) + { + size32 += fieldAlignment32 - (size32 % fieldAlignment32); + } + + size32 += fieldSize32; + maxFieldAlignment32 = Math.Max(maxFieldAlignment32, fieldAlignment32); + maxFieldSize32 = Math.Max(maxFieldSize32, fieldSize32); + } + + if (!ignoreFieldSize64) + { + if ((fieldAlignment64 == -1) || (alignment64 < 4)) + { + fieldAlignment64 = Math.Max(Math.Min(alignment64, fieldSize64), 1); + } + + if ((size64 % fieldAlignment64) != 0) + { + size64 += fieldAlignment64 - (size64 % fieldAlignment64); + } + + size64 += fieldSize64; + maxFieldAlignment64 = Math.Max(maxFieldAlignment64, fieldAlignment64); + maxFieldSize64 = Math.Max(maxFieldSize64, fieldSize64); + } + } + + if ((alignment32 == 8) && !anyFieldIs8BytePrimitive) + { + alignment32 = Math.Min(alignment32, maxFieldAlignment32); + } + + if ((alignment64 == 4) && !anyFieldIs8BytePrimitive) + { + alignment64 = Math.Max(alignment64, maxFieldAlignment64); + } + + if (recordType.Decl.IsUnion) + { + size32 = maxFieldSize32; + size64 = maxFieldSize64; + } + + if ((size32 % alignment32) != 0) + { + size32 += alignment32 - (size32 % alignment32); + } + + if ((size64 % alignment64) != 0) + { + size64 += alignment64 - (size64 % alignment64); + } + + has8BytePrimitiveField |= anyFieldIs8BytePrimitive; + } + else if (type is TypedefType typedefType) + { + // We check remapped names here so that types that have variable sizes + // can be treated correctly. Otherwise, they will resolve to a particular + // platform size, based on whatever parameters were passed into clang. + + var name = GetTypeName(cursor, context: null, type: type, ignoreTransparentStructsWhereRequired: false, isTemplate: false, nativeTypeName: out _); + var remappedName = GetRemappedTypeName(cursor, context: null, type, out _, skipUsing: true, ignoreTransparentStructsWhereRequired: false); + + if ((remappedName == name) && _config.WithTransparentStructs.TryGetValue(remappedName, out var transparentStruct) && (transparentStruct.Name.Equals("long", StringComparison.Ordinal) || transparentStruct.Name.Equals("ulong", StringComparison.Ordinal))) + { + size32 = 8; + size64 = 8; + + if (alignment32 == -1) + { + alignment32 = 8; + } + + if (alignment64 == -1) + { + alignment64 = 8; + } + + has8BytePrimitiveField = true; + } + else if (remappedName.Equals("IntPtr", StringComparison.Ordinal) || + remappedName.Equals("nint", StringComparison.Ordinal) || + remappedName.Equals("nuint", StringComparison.Ordinal) || + remappedName.Equals("UIntPtr", StringComparison.Ordinal) || + remappedName.EndsWith('*')) + { + size32 = 4; + size64 = 8; + + if (alignment32 == -1) + { + alignment32 = 4; + } + + if (alignment64 == -1) + { + alignment64 = 8; + } + } + else + { + GetTypeSize(cursor, typedefType.Decl.UnderlyingType, ref alignment32, ref alignment64, ref has8BytePrimitiveField, out size32, out size64); + } + } + else if (type is SubstTemplateTypeParmType substTemplateTypeParmType) + { + GetTypeSize(cursor, substTemplateTypeParmType.ReplacementType, ref alignment32, ref alignment64, ref has8BytePrimitiveField, out size32, out size64); + } + else if (type is TemplateSpecializationType templateSpecializationType) + { + if (templateSpecializationType.IsTypeAlias) + { + GetTypeSize(cursor, templateSpecializationType.AliasedType, ref alignment32, ref alignment64, ref has8BytePrimitiveField, out size32, out size64); + } + else if (templateSpecializationType.IsSugared) + { + GetTypeSize(cursor, templateSpecializationType.Desugar, ref alignment32, ref alignment64, ref has8BytePrimitiveField, out size32, out size64); + } + else if (templateSpecializationType.TemplateName.AsTemplateDecl is TemplateDecl templateDecl) + { + if (templateDecl.TemplatedDecl is TypeDecl typeDecl) + { + GetTypeSize(cursor, typeDecl.TypeForDecl, ref alignment32, ref alignment64, ref has8BytePrimitiveField, out size32, out size64); + } + else + { + AddDiagnostic(DiagnosticLevel.Error, $"Unsupported template specialization declaration kind: '{templateDecl.TemplatedDecl.DeclKindName}'.", cursor); + } + } + else + { + AddDiagnostic(DiagnosticLevel.Error, $"Unsupported template specialization type: '{templateSpecializationType}'.", cursor); + } + } + else if (type is TemplateTypeParmType) + { + // Nothing to handle + } + else + { + AddDiagnostic(DiagnosticLevel.Error, $"Unsupported type: '{type.TypeClass}'.", cursor); + } + } +} diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs index a96353b2..843afc2c 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs @@ -2795,3536 +2795,115 @@ private CallConv GetCallingConvention(Cursor? cursor, Cursor? context, Type type } } - private string GetCursorName(NamedDecl namedDecl) + private unsafe ReadOnlySpan GetFileContents(CXTranslationUnit translationUnit, CXFile file) { - if (!_cursorNames.TryGetValue(namedDecl, out var nameString)) + if (!_fileContents.TryGetValue(file, out var fileContentsMetadata)) { - nameString = namedDecl.Name.NormalizePath(); - var name = nameString.AsSpan(); - - // strip the prefix - if (name.StartsWith("enum ", StringComparison.Ordinal)) - { - name = name[5..]; - nameString = null; - } - else if (name.StartsWith("struct ", StringComparison.Ordinal)) - { - name = name[7..]; - nameString = null; - } - else if (name.StartsWith("union ", StringComparison.Ordinal)) - { - name = name[6..]; - nameString = null; - } - - var anonymousNameStartIndex = name.IndexOf("::(", StringComparison.Ordinal); - - if (anonymousNameStartIndex != -1) - { - anonymousNameStartIndex += 2; - name = name[anonymousNameStartIndex..]; - nameString = null; - } - - if (namedDecl is CXXConstructorDecl cxxConstructorDecl) - { - var parent = cxxConstructorDecl.Parent; - Debug.Assert(parent is not null); - - nameString = GetCursorName(parent); - name = nameString; - } - else if (namedDecl is CXXDestructorDecl cxxDestructorDecl) - { - var parent = cxxDestructorDecl.Parent; - Debug.Assert(parent is not null); - - nameString = $"~{GetCursorName(parent)}"; - name = nameString; - } - else if (name.IsWhiteSpace() || name.StartsWith('(')) - { -#if DEBUG - if (name.StartsWith('(')) - { - Debug.Assert(name.StartsWith("(anonymous enum at ", StringComparison.Ordinal) || - name.StartsWith("(anonymous struct at ", StringComparison.Ordinal) || - name.StartsWith("(anonymous union at ", StringComparison.Ordinal) || - name.StartsWith("(unnamed enum at ", StringComparison.Ordinal) || - name.StartsWith("(unnamed struct at ", StringComparison.Ordinal) || - name.StartsWith("(unnamed union at ", StringComparison.Ordinal) || - name.StartsWith("(unnamed at ", StringComparison.Ordinal)); - Debug.Assert(name.EndsWith(')')); - } -#endif - - if (namedDecl is TypeDecl typeDecl) - { - nameString = (typeDecl is TagDecl tagDecl) && tagDecl.Handle.IsAnonymous - ? GetAnonymousName(tagDecl, tagDecl.TypeForDecl.KindSpelling) - : GetTypeName(namedDecl, context: null, type: typeDecl.TypeForDecl, ignoreTransparentStructsWhereRequired: false, isTemplate: false, nativeTypeName: out _); - name = nameString; - } - else if (namedDecl is ParmVarDecl) - { - nameString = "param"; - name = nameString; - } - else if (namedDecl is FieldDecl fieldDecl) - { - nameString = GetAnonymousName(fieldDecl, fieldDecl.CursorKindSpelling); - name = nameString; - } - else - { - AddDiagnostic(DiagnosticLevel.Error, $"Unsupported anonymous named declaration: '{namedDecl.DeclKindName}'.", namedDecl); - } - } - - nameString ??= name.ToString(); - _cursorNames[namedDecl] = nameString; + var fileContents = translationUnit.GetFileContents(file, out _); + fileContentsMetadata = ((nuint)Unsafe.AsPointer(ref MemoryMarshal.GetReference(fileContents)), (uint)fileContents.Length); + _fileContents[file] = fileContentsMetadata; } - Debug.Assert(!string.IsNullOrWhiteSpace(nameString)); - return nameString; + return new ReadOnlySpan((byte*)fileContentsMetadata.Address, (int)fileContentsMetadata.Length); } - private string GetCursorQualifiedName(NamedDecl namedDecl, bool truncateParameters = false) + private string GetSourceRangeContents(CXTranslationUnit translationUnit, CXSourceRange sourceRange) { - if (!_cursorQualifiedNames.TryGetValue((namedDecl, truncateParameters), out var qualifiedName)) - { - var parts = new Stack(); - Decl? decl = namedDecl; - - do - { - if (decl is NamedDecl parentNamedDecl) - { - parts.Push(parentNamedDecl); - } - - if ((decl.DeclContext is null) && (decl is CXXMethodDecl cxxMethodDecl)) - { - var cxxRecordDecl = cxxMethodDecl.ThisObjectType.AsCXXRecordDecl; - Debug.Assert(cxxRecordDecl is not null); - decl = cxxRecordDecl; - } - else - { - decl = (Decl?)decl.DeclContext; - } - } - while (decl is not null); - - var qualifiedNameBuilder = new StringBuilder(); - - var part = parts.Pop(); - - while (parts.Count != 0) - { - AppendNamedDecl(part, GetCursorName(part), qualifiedNameBuilder); - _ = qualifiedNameBuilder.Append("::"); - part = parts.Pop(); - } - - AppendNamedDecl(part, GetCursorName(part), qualifiedNameBuilder); - - qualifiedName = qualifiedNameBuilder.ToString(); - _cursorQualifiedNames[(namedDecl, truncateParameters)] = qualifiedName; - } - - Debug.Assert(!string.IsNullOrWhiteSpace(qualifiedName)); - return qualifiedName; - - void AppendFunctionParameters(CXType functionType, StringBuilder qualifiedName) - { - if (truncateParameters) - { - return; - } - - _ = qualifiedName.Append('('); - - if (functionType.NumArgTypes != 0) - { - _ = qualifiedName.Append(functionType.GetArgType(0).Spelling); - - for (uint i = 1; i < functionType.NumArgTypes; i++) - { - _ = qualifiedName.Append(','); - _ = qualifiedName.Append(' '); - _ = qualifiedName.Append(functionType.GetArgType(i).Spelling); - } - } - - _ = qualifiedName.Append(')'); - _ = qualifiedName.Append(':'); - - _ = qualifiedName.Append(functionType.ResultType.Spelling); - - if (functionType.ExceptionSpecificationType == CXCursor_ExceptionSpecificationKind.CXCursor_ExceptionSpecificationKind_NoThrow) - { - _ = qualifiedName.Append(' '); - _ = qualifiedName.Append("nothrow"); - } - } + sourceRange.Start.GetFileLocation(out var startFile, out _, out _, out var startOffset); + sourceRange.End.GetFileLocation(out var endFile, out _, out _, out var endOffset); - void AppendNamedDecl(NamedDecl namedDecl, string name, StringBuilder qualifiedName) + if (startFile != endFile) { - _ = qualifiedName.Append(name); - - if (namedDecl is FunctionDecl functionDecl) - { - AppendFunctionParameters(functionDecl.Type.Handle, qualifiedName); - } - else if (namedDecl is TemplateDecl templateDecl) - { - AppendTemplateParameters(templateDecl, qualifiedName); - - if (namedDecl is FunctionTemplateDecl functionTemplateDecl) - { - AppendFunctionParameters(functionTemplateDecl.Handle.Type, qualifiedName); - } - } - else if (namedDecl is ClassTemplateSpecializationDecl classTemplateSpecializationDecl) - { - AppendTemplateArguments(classTemplateSpecializationDecl, qualifiedName); - } + return string.Empty; } - void AppendTemplateArgument(TemplateArgument templateArgument, StringBuilder qualifiedName) - { - switch (templateArgument.Kind) - { - case CXTemplateArgumentKind_Type: - { - _ = qualifiedName.Append(templateArgument.AsType.AsString); - break; - } + var contents1 = GetFileContents(translationUnit, startFile); + var contents = contents1.Slice(unchecked((int)startOffset), unchecked((int)(endOffset - startOffset))); + return Encoding.UTF8.GetString(contents); + } - case CXTemplateArgumentKind_Integral: - { - _ = qualifiedName.Append(templateArgument.AsIntegral); - break; - } + private bool HasSuppressGCTransition(Cursor? cursor) + => (cursor is NamedDecl namedDecl) && HasRemapping(namedDecl, _config._withSuppressGCTransitions); - default: - { - _ = qualifiedName.Append('?'); - break; - } - } - } + private bool HasBaseField(CXXRecordDecl cxxRecordDecl) + { + var hasBaseField = false; - void AppendTemplateArguments(ClassTemplateSpecializationDecl classTemplateSpecializationDecl, StringBuilder qualifiedName) + foreach (var cxxBaseSpecifier in cxxRecordDecl.Bases) { - if (truncateParameters) - { - return; - } - - _ = qualifiedName.Append('<'); - - var templateArgs = classTemplateSpecializationDecl.TemplateArgs; + var baseCxxRecordDecl = GetRecordDecl(cxxBaseSpecifier); - if (templateArgs.Any()) + if (HasField(baseCxxRecordDecl)) { - AppendTemplateArgument(templateArgs[0], qualifiedName); - - for (var i = 1; i < templateArgs.Count; i++) - { - _ = qualifiedName.Append(','); - _ = qualifiedName.Append(' '); - AppendTemplateArgument(templateArgs[i], qualifiedName); - } + hasBaseField = true; + break; } - - _ = qualifiedName.Append('>'); } - void AppendTemplateParameters(TemplateDecl templateDecl, StringBuilder qualifiedName) - { - if (truncateParameters) - { - return; - } - - _ = qualifiedName.Append('<'); - - var templateParameters = templateDecl.TemplateParameters; - - if (templateParameters.Any()) - { - _ = qualifiedName.Append(templateParameters[0].Name); - - for (var i = 1; i < templateParameters.Count; i++) - { - _ = qualifiedName.Append(','); - _ = qualifiedName.Append(' '); - _ = qualifiedName.Append(templateParameters[i].Name); - } - } - - _ = qualifiedName.Append('>'); - } + return hasBaseField; } - private static Expr GetExprAsWritten(Expr expr, bool removeParens) + private bool HasField(RecordDecl recordDecl) { - do + var hasField = recordDecl.Fields.Any() || recordDecl.Decls.Any((decl) => (decl is RecordDecl nestedRecordDecl) && nestedRecordDecl.IsAnonymousStructOrUnion && HasField(nestedRecordDecl)); + + if (!hasField && (recordDecl is CXXRecordDecl cxxRecordDecl)) { - if (expr is ImplicitCastExpr implicitCastExpr) - { - expr = implicitCastExpr.SubExprAsWritten; - } - else if (removeParens && (expr is ParenExpr parenExpr)) - { - expr = parenExpr.SubExpr; - } - else - { - return expr; - } + hasField = HasBaseField(cxxRecordDecl); } - while (true); + + return hasField; } - private uint GetOverloadIndex(CXXMethodDecl cxxMethodDeclToMatch) + private bool HasUnsafeMethod(CXXRecordDecl cxxRecordDecl) { - if (!_overloadIndices.TryGetValue(cxxMethodDeclToMatch, out var index)) - { - var parent = cxxMethodDeclToMatch.Parent; - Debug.Assert(parent is not null); - - index = GetOverloadIndex(cxxMethodDeclToMatch, parent, baseIndex: 0); - _overloadIndices.Add(cxxMethodDeclToMatch, index); - } - return index; + var hasUnsafeMethod = cxxRecordDecl.Methods.Any((method) => method.IsUserProvided && IsUnsafe(method) && !IsExcluded(method)); - uint GetOverloadIndex(CXXMethodDecl cxxMethodDeclToMatch, CXXRecordDecl cxxRecordDecl, uint baseIndex) + if (!hasUnsafeMethod) { - var index = baseIndex; - foreach (var cxxBaseSpecifier in cxxRecordDecl.Bases) { var baseCxxRecordDecl = GetRecordDecl(cxxBaseSpecifier); - index = GetOverloadIndex(cxxMethodDeclToMatch, baseCxxRecordDecl, index); - } - foreach (var cxxMethodDecl in cxxRecordDecl.Methods.OrderBy((cxxmd) => cxxmd.VtblIndex)) - { - if (IsExcluded(cxxMethodDecl)) - { - continue; - } - else if (cxxMethodDecl == cxxMethodDeclToMatch) + if (HasUnsafeMethod(baseCxxRecordDecl)) { + hasUnsafeMethod = true; break; } - else if (cxxMethodDecl.Name == cxxMethodDeclToMatch.Name) - { - index++; - } } - - return index; } + + return hasUnsafeMethod; } - private uint GetOverloadCount(CXXMethodDecl cxxMethodDeclToMatch) + private bool HasVtbl(CXXRecordDecl cxxRecordDecl, out bool hasBaseVtbl) { - var parent = cxxMethodDeclToMatch.Parent; - Debug.Assert(parent is not null); - - return GetOverloadIndex(cxxMethodDeclToMatch, parent, baseCount: 0); + var hasVtbl = cxxRecordDecl.Methods.Any((method) => method.IsVirtual && method.IsVirtual && (method.OverriddenMethods.Count == 0)); + hasBaseVtbl = false; - uint GetOverloadIndex(CXXMethodDecl cxxMethodDeclToMatch, CXXRecordDecl cxxRecordDecl, uint baseCount) + if (!hasVtbl) { - var count = baseCount; + var indirectVtblCount = 0; foreach (var cxxBaseSpecifier in cxxRecordDecl.Bases) { var baseCxxRecordDecl = GetRecordDecl(cxxBaseSpecifier); - count = GetOverloadIndex(cxxMethodDeclToMatch, baseCxxRecordDecl, count); - } - foreach (var cxxMethodDecl in cxxRecordDecl.Methods) - { - if (IsExcluded(cxxMethodDecl)) - { - continue; - } - else if (cxxMethodDecl.Name == cxxMethodDeclToMatch.Name) + if ((HasVtbl(baseCxxRecordDecl, out var baseHasBaseVtbl) || baseHasBaseVtbl) && !HasField(baseCxxRecordDecl)) { - count++; + indirectVtblCount++; } } - return count; - } - } - - private CXXRecordDecl GetRecordDecl(CXXBaseSpecifier cxxBaseSpecifier) - { - var baseType = cxxBaseSpecifier.Type; - - if (IsType(cxxBaseSpecifier, baseType, out var recordType)) - { - return (CXXRecordDecl)recordType.Decl; - } - - AddDiagnostic(DiagnosticLevel.Error, "Failed to retrieve record type for CXX base specifier. Falling back to referenced type.", cxxBaseSpecifier); - return (CXXRecordDecl)cxxBaseSpecifier.Referenced; - } - - private string GetRemappedCursorName(NamedDecl namedDecl) => GetRemappedCursorName(namedDecl, out _, skipUsing: false); - - private string GetRemappedCursorName(NamedDecl namedDecl, out string nativeTypeName, bool skipUsing) - { - nativeTypeName = GetCursorQualifiedName(namedDecl); - - var name = nativeTypeName; - var remappedName = GetRemappedName(name, namedDecl, tryRemapOperatorName: true, out var wasRemapped, skipUsing); - - if (wasRemapped) - { - return remappedName; - } - - name = GetCursorQualifiedName(namedDecl, truncateParameters: true); - remappedName = GetRemappedName(name, namedDecl, tryRemapOperatorName: true, out wasRemapped, skipUsing); - - if (wasRemapped) - { - return remappedName; - } - - name = GetCursorName(namedDecl); - remappedName = GetRemappedName(name, namedDecl, tryRemapOperatorName: true, out wasRemapped, skipUsing); - - if (wasRemapped) - { - return remappedName; - } - - if (namedDecl is CXXConstructorDecl cxxConstructorDecl) - { - var parent = cxxConstructorDecl.Parent; - Debug.Assert(parent is not null); - remappedName = GetRemappedCursorName(parent); - } - else if (namedDecl is CXXDestructorDecl) - { - remappedName = "Dispose"; - } - else if ((namedDecl is FieldDecl fieldDecl) && name.StartsWith("__AnonymousFieldDecl_", StringComparison.Ordinal)) - { - if (fieldDecl.Type.AsCXXRecordDecl?.IsAnonymousStructOrUnion == true) - { - // For fields of anonymous types, use the name of the type but clean off the type - // kind tag at the end. - var typeName = GetRemappedNameForAnonymousRecord(fieldDecl.Type.AsCXXRecordDecl); - var tagIndex = typeName.LastIndexOf("_e__", StringComparison.Ordinal); - Debug.Assert(typeName[0] == '_'); - Debug.Assert(tagIndex >= 0); - remappedName = typeName.Substring(1, tagIndex - 1); - } - else + if (indirectVtblCount > 1) { - remappedName = "Anonymous"; - - var parent = fieldDecl.Parent; - Debug.Assert(parent is not null); - - if (parent.AnonymousFields.Count > 1) - { - var index = parent.AnonymousFields.IndexOf(fieldDecl) + 1; - remappedName += index.ToString(CultureInfo.InvariantCulture); - } + AddDiagnostic(DiagnosticLevel.Warning, "Unsupported cxx record declaration: 'multiple virtual bases'. Generated bindings may be incomplete.", cxxRecordDecl); } - } - else if ((namedDecl is RecordDecl recordDecl) && name.StartsWith("__AnonymousRecord_", StringComparison.Ordinal)) - { - remappedName = GetRemappedNameForAnonymousRecord(recordDecl); - } - - return remappedName; - } - - private static int GetAnonymousRecordIndex(RecordDecl recordDecl, RecordDecl parentRecordDecl) - { - var index = -1; - var parentAnonRecordCount = parentRecordDecl.AnonymousRecords.Count; - - if (parentAnonRecordCount != 0) - { - index = parentRecordDecl.AnonymousRecords.IndexOf(recordDecl); - if (index != -1) - { - if (parentAnonRecordCount > 1) - { - index++; - } - - if (parentRecordDecl.Parent is RecordDecl grandparentRecordDecl) - { - var parentIndex = GetAnonymousRecordIndex(parentRecordDecl, grandparentRecordDecl); - - // We can't have the nested anonymous record have the same name as the parent - // so skip that index and just go one higher instead. This could still conflict - // with another anonymous record at a different level, but that is less likely - // and will still be unambiguous in total. - - if ((parentIndex == index) || ((parentIndex > 0) && (index > parentIndex))) - { - if (recordDecl.IsUnion == parentRecordDecl.IsUnion) - { - index++; - } - } - } - } + hasBaseVtbl = indirectVtblCount != 0; } - return index; - } - - private string GetRemappedNameForAnonymousRecord(RecordDecl recordDecl) - { - if (recordDecl.Parent is RecordDecl parentRecordDecl) - { - var remappedNameBuilder = new StringBuilder(); - var matchingField = null as FieldDecl; - - if (!recordDecl.IsAnonymousStructOrUnion) - { - matchingField = parentRecordDecl.Fields.Where((fieldDecl) => { - var fieldType = fieldDecl.Type.CanonicalType; - - if (fieldType is ArrayType arrayType) - { - fieldType = arrayType.ElementType.CanonicalType; - } - - return fieldType == recordDecl.TypeForDecl.CanonicalType; - }).FirstOrDefault(); - } - - if ((matchingField is not null) && !matchingField.IsAnonymousField) - { - _ = remappedNameBuilder.Append('_'); - _ = remappedNameBuilder.Append(GetRemappedCursorName(matchingField)); - } - else - { - _ = remappedNameBuilder.Append("_Anonymous"); - - var index = GetAnonymousRecordIndex(recordDecl, parentRecordDecl); - - if (index != 0) - { - _ = remappedNameBuilder.Append(index); - } - } - - // Add the type kind tag. - _ = remappedNameBuilder.Append("_e__"); - _ = remappedNameBuilder.Append(recordDecl.IsUnion ? "Union" : "Struct"); - return remappedNameBuilder.ToString(); - } - else - { - return $"_Anonymous_e__{(recordDecl.IsUnion ? "Union" : "Struct")}"; - } - } - - private string GetRemappedName(string name, Cursor? cursor, bool tryRemapOperatorName, out bool wasRemapped, bool skipUsing = false) - => GetRemappedName(name, cursor, tryRemapOperatorName, out wasRemapped, skipUsing, skipUsingIfNotRemapped: skipUsing); - - private string GetRemappedName(string name, Cursor? cursor, bool tryRemapOperatorName, out bool wasRemapped, bool skipUsing, bool skipUsingIfNotRemapped) - { - var remappedNamesLookup = _config._remappedNames.GetAlternateLookup>(); - - if (remappedNamesLookup.TryGetValue(name, out var remappedName)) - { - wasRemapped = true; - _ = _usedRemappings.Add(name); - return AddUsingDirectiveIfNeeded(_outputBuilder, remappedName, skipUsing); - } - - if (name.StartsWith("const ", StringComparison.Ordinal)) - { - var tmpName = name.AsSpan()[6..]; - - if (remappedNamesLookup.TryGetValue(tmpName, out remappedName)) - { - - wasRemapped = true; - _ = _usedRemappings.Add(tmpName.ToString()); - return AddUsingDirectiveIfNeeded(_outputBuilder, remappedName, skipUsing); - } - } - - remappedName = name; - - if ((cursor is FunctionDecl functionDecl) && tryRemapOperatorName && TryRemapOperatorName(ref remappedName, functionDecl)) - { - wasRemapped = true; - // We don't track remapped operators in _usedRemappings - return AddUsingDirectiveIfNeeded(_outputBuilder, remappedName, skipUsing); - } - - if ((cursor is CXXBaseSpecifier cxxBaseSpecifier) && remappedName.StartsWith("__AnonymousBase_", StringComparison.Ordinal)) - { - Debug.Assert(_cxxRecordDeclContext is not null); - remappedName = "Base"; - - if (_cxxRecordDeclContext.Bases.Count > 1) - { - var index = _cxxRecordDeclContext.Bases.IndexOf(cxxBaseSpecifier) + 1; - remappedName += index.ToString(CultureInfo.InvariantCulture); - } - - wasRemapped = true; - return AddUsingDirectiveIfNeeded(_outputBuilder, remappedName, skipUsing); - } - - wasRemapped = false; - return AddUsingDirectiveIfNeeded(_outputBuilder, remappedName, skipUsingIfNotRemapped); - - string AddUsingDirectiveIfNeeded(IOutputBuilder? outputBuilder, string remappedName, bool skipUsing) - { - if (!skipUsing) - { - if (NeedsSystemSupportRegex().IsMatch(remappedName)) - { - outputBuilder?.EmitSystemSupport(); - } - - var namespaceName = GetNamespace(remappedName); - AddUsingDirective(outputBuilder, namespaceName); - } - - return remappedName; - } - } - - private string GetRemappedTypeName(Cursor? cursor, Cursor? context, Type type, out string nativeTypeName, bool skipUsing = false, bool ignoreTransparentStructsWhereRequired = false, bool isTemplate = false) - { - var name = GetTypeName(cursor, context, type, ignoreTransparentStructsWhereRequired, isTemplate: isTemplate, nativeTypeName: out nativeTypeName); - - var nameToCheck = nativeTypeName; - var remappedName = GetRemappedName(nameToCheck, cursor, tryRemapOperatorName: false, out var wasRemapped, skipUsing, skipUsingIfNotRemapped: true); - - if (!wasRemapped) - { - nameToCheck = name; - remappedName = GetRemappedName(nameToCheck, cursor, tryRemapOperatorName: false, out wasRemapped, skipUsing); - - if (!wasRemapped) - { - if (IsTypeConstantOrIncompleteArray(cursor, type, out var arrayType) && IsType(cursor, arrayType.ElementType)) - { - type = arrayType.ElementType; - } - - if (IsType(cursor, type, out var recordType) && remappedName.StartsWith("__AnonymousRecord_", StringComparison.Ordinal)) - { - var recordDecl = recordType.Decl; - remappedName = GetRemappedNameForAnonymousRecord(recordDecl); - } - else if (IsType(cursor, type, out var enumType) && remappedName.StartsWith("__AnonymousEnum_", StringComparison.Ordinal)) - { - remappedName = GetRemappedTypeName(enumType.Decl, context: null, enumType.Decl.IntegerType, out _, skipUsing); - } - else if (cursor is EnumDecl enumDecl) - { - // Even though some types have entries with names like *_FORCE_DWORD or *_FORCE_UINT - // MSVC and Clang both still treat this as "signed" values and thus we don't want - // to specially handle it as uint, as that can break ABI handling on some platforms. - - WithType(enumDecl, ref remappedName, ref nativeTypeName); - } - } - } - - if (string.IsNullOrWhiteSpace(nativeTypeName)) - { - // When we have an empty native type name, it means the original - // name is the same as the native type name and no adjustments - // were made. In order to ensure things are correctly preserved - // we need to ensure its propagated back here so the below comparison - // works and we don't end up comparing "empty" vs "remapped" - nativeTypeName = name; - } - - if (IsNativeTypeNameEquivalent(nativeTypeName, remappedName)) - { - // Empty the native type name if its equivalent to the new name - nativeTypeName = string.Empty; - } - - return remappedName; - } - - private unsafe ReadOnlySpan GetFileContents(CXTranslationUnit translationUnit, CXFile file) - { - if (!_fileContents.TryGetValue(file, out var fileContentsMetadata)) - { - var fileContents = translationUnit.GetFileContents(file, out _); - fileContentsMetadata = ((nuint)Unsafe.AsPointer(ref MemoryMarshal.GetReference(fileContents)), (uint)fileContents.Length); - _fileContents[file] = fileContentsMetadata; - } - - return new ReadOnlySpan((byte*)fileContentsMetadata.Address, (int)fileContentsMetadata.Length); - } - - private string GetSourceRangeContents(CXTranslationUnit translationUnit, CXSourceRange sourceRange) - { - sourceRange.Start.GetFileLocation(out var startFile, out _, out _, out var startOffset); - sourceRange.End.GetFileLocation(out var endFile, out _, out _, out var endOffset); - - if (startFile != endFile) - { - return string.Empty; - } - - var contents1 = GetFileContents(translationUnit, startFile); - var contents = contents1.Slice(unchecked((int)startOffset), unchecked((int)(endOffset - startOffset))); - return Encoding.UTF8.GetString(contents); - } - - private string GetTargetTypeName(Cursor cursor, out string nativeTypeName) - { - var targetTypeName = ""; - nativeTypeName = ""; - - if (cursor is Decl decl) - { - if (decl is EnumConstantDecl enumConstantDecl) - { - targetTypeName = enumConstantDecl.DeclContext is EnumDecl enumDecl - ? GetRemappedTypeName(enumDecl, context: null, enumDecl.IntegerType, out nativeTypeName) - : GetRemappedTypeName(enumConstantDecl, context: null, enumConstantDecl.Type, out nativeTypeName); - } - else if (decl is TypeDecl previousTypeDecl) - { - targetTypeName = GetRemappedTypeName(previousTypeDecl, context: null, previousTypeDecl.TypeForDecl, out nativeTypeName); - } - else if (decl is VarDecl varDecl) - { - if (varDecl is ParmVarDecl parmVarDecl) - { - targetTypeName = GetRemappedTypeName(parmVarDecl, context: null, parmVarDecl.Type, out nativeTypeName); - - if (!_config.GenerateDisableRuntimeMarshalling && (parmVarDecl.ParentFunctionOrMethod is FunctionDecl functionDecl) && (((functionDecl is CXXMethodDecl cxxMethodDecl) && cxxMethodDecl.IsVirtual) || (functionDecl.Body is null)) && targetTypeName.Equals("bool", StringComparison.Ordinal)) - { - // bool is not blittable when DisableRuntimeMarshalling is not specified, so we shouldn't use it for P/Invoke signatures - targetTypeName = "byte"; - nativeTypeName = string.IsNullOrWhiteSpace(nativeTypeName) ? "bool" : nativeTypeName; - } - } - else - { - var type = varDecl.Type; - var cursorName = GetCursorName(varDecl).AsSpan(); - - if (cursorName.StartsWith("ClangSharpMacro_", StringComparison.Ordinal)) - { - cursorName = cursorName["ClangSharpMacro_".Length..]; - - if (_config._withTypes.GetAlternateLookup>().TryGetValue(cursorName, out targetTypeName)) - { - return targetTypeName; - } - - type = varDecl.Init.Type; - } - - targetTypeName = GetRemappedTypeName(varDecl, context: null, type, out nativeTypeName); - } - } - - } - else if ((cursor is Expr expr) && (expr is not MemberExpr)) - { - targetTypeName = GetRemappedTypeName(expr, context: null, expr.Type, out nativeTypeName); - } - - return targetTypeName; - } - - private string GetTypeName(Cursor? cursor, Cursor? context, Type type, bool ignoreTransparentStructsWhereRequired, bool isTemplate, out string nativeTypeName) - { - if (_typeNames.TryGetValue((cursor, context, type), out var result)) - { - nativeTypeName = result.nativeTypeName; - return result.typeName; - } - else if (IsType(cursor, type, out var tagType) && tagType.Decl.Handle.IsAnonymous) - { - // In order to avoid minor path differences, casing, and other deltas across different - // invocations of the tool, we want to use the "built" anonymous name so we get a more - // minimal but still accurate set of information embedded in the output. - - result.typeName = GetAnonymousName(tagType.Decl, tagType.KindSpelling); - result.nativeTypeName = result.typeName; - - _typeNames[(cursor, context, type)] = result; - - nativeTypeName = result.nativeTypeName; - return result.typeName; - } - else - { - return GetTypeName(cursor, context, type, type, ignoreTransparentStructsWhereRequired, isTemplate, out nativeTypeName); - } - } - - private string GetTypeName(Cursor? cursor, Cursor? context, Type rootType, Type type, bool ignoreTransparentStructsWhereRequired, bool isTemplate, out string nativeTypeName) - { - if (!_typeNames.TryGetValue((cursor, context, type), out var result)) - { - result.typeName = type.AsString.NormalizePath() - .Replace("unnamed enum at", "anonymous enum at", StringComparison.Ordinal) - .Replace("unnamed struct at", "anonymous struct at", StringComparison.Ordinal) - .Replace("unnamed union at", "anonymous union at", StringComparison.Ordinal); - - result.nativeTypeName = result.typeName; - - // We don't want to handle these using IsType because we need to specially - // handle cases like TypedefType at each level of the type hierarchy - - if (type is ArrayType arrayType) - { - result.typeName = GetRemappedTypeName(cursor, context, arrayType.ElementType, out _, skipUsing: true, ignoreTransparentStructsWhereRequired); - - if (cursor is FunctionDecl or ParmVarDecl) - { - result.typeName += '*'; - } - } - else if (type is AttributedType attributedType) - { - result.typeName = GetTypeName(cursor, context, rootType, attributedType.ModifiedType, ignoreTransparentStructsWhereRequired, isTemplate, out _); - } - else if (type is BuiltinType) - { - switch (type.Kind) - { - case CXType_Void: - { - result.typeName = (cursor is null) ? "Void" : "void"; - break; - } - - case CXType_Bool: - { - result.typeName = (cursor is null) ? "Boolean" : "bool"; - break; - } - - case CXType_Char_U: - case CXType_UChar: - { - result.typeName = (cursor is null) ? "Byte" : "byte"; - break; - } - - case CXType_Char16: - { - if (_config.GenerateDisableRuntimeMarshalling) - { - result.typeName = (cursor is null) ? "Char" : "char"; - break; - } - goto case CXType_UShort; - } - - case CXType_UShort: - { - result.typeName = (cursor is null) ? "UInt16" : "ushort"; - break; - } - - case CXType_UInt: - { - result.typeName = (cursor is null) ? "UInt32" : "uint"; - break; - } - - case CXType_ULong: - { - if (_config.GenerateUnixTypes) - { - result.typeName = _config.ExcludeNIntCodegen ? "UIntPtr" : "nuint"; - } - else - { - goto case CXType_UInt; - } - break; - } - - case CXType_ULongLong: - { - result.typeName = (cursor is null) ? "UInt64" : "ulong"; - break; - } - - case CXType_Char_S: - case CXType_SChar: - { - result.typeName = (cursor is null) ? "SByte" : "sbyte"; - break; - } - - case CXType_WChar: - { - if (_config.GenerateUnixTypes) - { - goto case CXType_UInt; - } - else - { - goto case CXType_Char16; - } - } - - case CXType_Short: - { - result.typeName = (cursor is null) ? "Int16" : "short"; - break; - } - - case CXType_Int: - { - result.typeName = (cursor is null) ? "Int32" : "int"; - break; - } - - case CXType_Long: - { - if (_config.GenerateUnixTypes) - { - result.typeName = _config.ExcludeNIntCodegen ? "IntPtr" : "nint"; - } - else - { - goto case CXType_Int; - } - break; - } - - case CXType_LongLong: - { - result.typeName = (cursor is null) ? "Int64" : "long"; - break; - } - - case CXType_Float: - { - result.typeName = (cursor is null) ? "Single" : "float"; - break; - } - - case CXType_Double: - { - result.typeName = (cursor is null) ? "Double" : "double"; - break; - } - - case CXType_NullPtr: - { - result.typeName = "null"; - break; - } - - default: - { - AddDiagnostic(DiagnosticLevel.Warning, $"Unsupported builtin type: '{type.KindSpelling}'. Falling back '{result.typeName}'.", cursor); - break; - } - } - } - else if (type is DecltypeType decltypeType) - { - result.typeName = GetTypeName(cursor, context, rootType, decltypeType.UnderlyingType, ignoreTransparentStructsWhereRequired, isTemplate, out _); - } - else if (type is DeducedType deducedType) - { - result.typeName = GetTypeName(cursor, context, rootType, deducedType.GetDeducedType, ignoreTransparentStructsWhereRequired, isTemplate, out _); - } - else if (type is DependentNameType dependentNameType) - { - if (dependentNameType.IsSugared) - { - result.typeName = GetTypeName(cursor, context, rootType, dependentNameType.Desugar, ignoreTransparentStructsWhereRequired, isTemplate, out _); - } - else - { - // The default name should be correct - } - } - else if (type is ElaboratedType elaboratedType) - { - result.typeName = GetTypeName(cursor, context, rootType, elaboratedType.NamedType, ignoreTransparentStructsWhereRequired, isTemplate, out var nativeNamedTypeName); - - if (!string.IsNullOrWhiteSpace(nativeNamedTypeName) && - !result.nativeTypeName.StartsWith("const ", StringComparison.Ordinal) && - !result.nativeTypeName.StartsWith("enum ", StringComparison.Ordinal) && - !result.nativeTypeName.StartsWith("struct ", StringComparison.Ordinal) && - !result.nativeTypeName.StartsWith("union ", StringComparison.Ordinal)) - { - result.nativeTypeName = nativeNamedTypeName; - } - } - else if (type is FunctionType functionType) - { - result.typeName = GetTypeNameForPointeeType(cursor, context, rootType, functionType, ignoreTransparentStructsWhereRequired, isTemplate, out _, out _); - } - else if (type is InjectedClassNameType injectedClassNameType) - { - result.typeName = GetTypeName(cursor, context, rootType, injectedClassNameType.InjectedTST, ignoreTransparentStructsWhereRequired, isTemplate, out _); - } - else if (type is PackExpansionType packExpansionType) - { - result.typeName = GetTypeName(cursor, context, rootType, packExpansionType.Pattern, ignoreTransparentStructsWhereRequired, isTemplate, out _); - } - else if (type is PointerType pointerType) - { - result.typeName = GetTypeNameForPointeeType(cursor, context, rootType, pointerType.PointeeType, ignoreTransparentStructsWhereRequired, isTemplate, out var nativePointeeTypeName, out var isAdjusted); - - if (isAdjusted) - { - result.nativeTypeName = $"{nativePointeeTypeName} *"; - } - } - else if (type is ReferenceType referenceType) - { - result.typeName = GetTypeNameForPointeeType(cursor, context, rootType, referenceType.PointeeType, ignoreTransparentStructsWhereRequired, isTemplate, out var nativePointeeTypeName, out var isAdjusted); - - if (isAdjusted) - { - result.nativeTypeName = $"{nativePointeeTypeName} &"; - } - } - else if (type is SubstTemplateTypeParmType substTemplateTypeParmType) - { - result.typeName = GetTypeName(cursor, context, rootType, substTemplateTypeParmType.ReplacementType, ignoreTransparentStructsWhereRequired, isTemplate, out _); - } - else if (type is TagType tagType) - { - if (tagType.Decl.Handle.IsAnonymous) - { - // In order to avoid minor path differences, casing, and other deltas across different - // invocations of the tool, we want to use the "built" anonymous name so we get a more - // minimal but still accurate set of information embedded in the output. - - result.typeName = GetAnonymousName(tagType.Decl, tagType.KindSpelling); - result.nativeTypeName = result.typeName; - } - else if (tagType.Handle.IsConstQualified) - { - result.typeName = GetTypeName(cursor, context, rootType, tagType.Decl.TypeForDecl, ignoreTransparentStructsWhereRequired, isTemplate, out _); - } - else - { - // The default name should be correct for C++, but C may have a prefix we need to strip - - if (result.typeName.StartsWith("enum ", StringComparison.Ordinal)) - { - result.typeName = result.typeName[5..]; - } - else if (result.typeName.StartsWith("struct ", StringComparison.Ordinal)) - { - result.typeName = result.typeName[7..]; - } - else if (result.typeName.StartsWith("union ", StringComparison.Ordinal)) - { - result.typeName = result.typeName[6..]; - } - } - - if (result.typeName.Contains("::", StringComparison.Ordinal)) - { - result.typeName = result.typeName.Split(s_doubleColonSeparator, StringSplitOptions.RemoveEmptyEntries).Last(); - result.typeName = GetRemappedName(result.typeName, cursor, tryRemapOperatorName: false, out _, skipUsing: true); - } - } - else if (type is TemplateSpecializationType templateSpecializationType) - { - var nameBuilder = new StringBuilder(); - - var templateTypeDecl = IsType(cursor, templateSpecializationType, out var recordType) - ? recordType.Decl - : (NamedDecl)templateSpecializationType.TemplateName.AsTemplateDecl; - - var templateTypeDeclName = GetRemappedCursorName(templateTypeDecl, out _, skipUsing: true); - var isStdAtomic = false; - - if (templateTypeDeclName.Equals("atomic", StringComparison.Ordinal)) - { - isStdAtomic = (templateTypeDecl.Parent is NamespaceDecl namespaceDecl) && namespaceDecl.IsStdNamespace; - } - - if (!isStdAtomic) - { - _ = nameBuilder.Append(templateTypeDeclName); - _ = nameBuilder.Append('<'); - } - else - { - _ = nameBuilder.Append("volatile "); - } - - var shouldWritePrecedingComma = false; - - foreach (var arg in templateSpecializationType.Args) - { - if (shouldWritePrecedingComma) - { - _ = nameBuilder.Append(','); - _ = nameBuilder.Append(' '); - } - - var typeName = ""; - - switch (arg.Kind) - { - case CXTemplateArgumentKind_Type: - { - typeName = GetRemappedTypeName(cursor, context: null, arg.AsType, out var nativeAsTypeName, skipUsing: true, isTemplate: true); - break; - } - - case CXTemplateArgumentKind_Expression: - { - var oldOutputBuilder = _outputBuilder; - _outputBuilder = new CSharpOutputBuilder("ClangSharp_TemplateSpecializationType_AsExpr", this); - - Visit(arg.AsExpr); - typeName = _outputBuilder.ToString() ?? ""; - - _outputBuilder = oldOutputBuilder; - break; - } - - default: - { - typeName = result.typeName; - AddDiagnostic(DiagnosticLevel.Warning, $"Unsupported template argument kind: '{arg.Kind}'. Falling back '{result.typeName}'.", cursor); - break; - } - } - - if (!_config.GenerateDisableRuntimeMarshalling && typeName.Equals("bool", StringComparison.Ordinal)) - { - // bool is not blittable when DisableRuntimeMarshalling is not specified, so we shouldn't use it for P/Invoke signatures - typeName = "byte"; - } - - if (typeName.EndsWith('*') || typeName.Contains("delegate*", StringComparison.Ordinal)) - { - if (Config.GenerateGenericPointerWrapper) - { - AddDiagnostic(DiagnosticLevel.Warning, $"Unhandled pointer in template: '{typeName}'. Falling back 'IntPtr'.", cursor); - } - - // Pointers are not yet supported as generic arguments; remap to IntPtr - typeName = "IntPtr"; - _outputBuilder?.EmitSystemSupport(); - } - - _ = nameBuilder.Append(typeName); - - shouldWritePrecedingComma = true; - } - - if (!isStdAtomic) - { - _ = nameBuilder.Append('>'); - } - - result.typeName = nameBuilder.ToString(); - } - else if (type is TemplateTypeParmType templateTypeParmType) - { - if (templateTypeParmType.IsSugared) - { - result.typeName = GetTypeName(cursor, context, rootType, templateTypeParmType.Desugar, ignoreTransparentStructsWhereRequired, isTemplate, out _); - } - else - { - // The default name should be correct - } - } - else if (type is TypedefType typedefType) - { - // We check remapped names here so that types that have variable sizes - // can be treated correctly. Otherwise, they will resolve to a particular - // platform size, based on whatever parameters were passed into clang. - - var remappedName = GetRemappedName(result.typeName, cursor, tryRemapOperatorName: false, out var wasRemapped, skipUsing: true); - result.typeName = wasRemapped ? remappedName : GetTypeName(cursor, context, rootType, typedefType.Decl.UnderlyingType, ignoreTransparentStructsWhereRequired, isTemplate, out _); - } - else if (type is UsingType usingType) - { - result.typeName = GetTypeName(cursor, context, rootType, usingType.Desugar, ignoreTransparentStructsWhereRequired, isTemplate, out _); - } - else - { - AddDiagnostic(DiagnosticLevel.Warning, $"Unsupported type: '{type.TypeClass}'. Falling back '{result.typeName}'.", cursor); - } - - Debug.Assert(!string.IsNullOrWhiteSpace(result.typeName)); - Debug.Assert(!string.IsNullOrWhiteSpace(result.nativeTypeName)); - - if (IsNativeTypeNameEquivalent(result.nativeTypeName, result.typeName)) - { - result.nativeTypeName = string.Empty; - } - - _typeNames[(cursor, context, type)] = result; - } - - nativeTypeName = result.nativeTypeName; - return result.typeName; - } - - private string GetTypeNameForPointeeType(Cursor? cursor, Cursor? context, Type rootType, Type pointeeType, bool ignoreTransparentStructsWhereRequired, bool isTemplate, out string nativePointeeTypeName, out bool isAdjusted) - { - var name = pointeeType.AsString; - - nativePointeeTypeName = name; - isAdjusted = false; - - // We don't want to handle these using IsType because we need to specially - // handle cases like TypedefType at each level of the type hierarchy - - if (pointeeType is AttributedType attributedType) - { - name = GetTypeNameForPointeeType(cursor, context, rootType, attributedType.ModifiedType, ignoreTransparentStructsWhereRequired, isTemplate, out var nativeModifiedTypeName, out isAdjusted); - } - else if (pointeeType is ElaboratedType elaboratedType) - { - name = GetTypeNameForPointeeType(cursor, context, rootType, elaboratedType.NamedType, ignoreTransparentStructsWhereRequired, isTemplate, out var nativeNamedTypeName, out isAdjusted); - - if (!string.IsNullOrWhiteSpace(nativeNamedTypeName) && - !nativePointeeTypeName.StartsWith("const ", StringComparison.Ordinal) && - !nativePointeeTypeName.StartsWith("enum ", StringComparison.Ordinal) && - !nativePointeeTypeName.StartsWith("struct ", StringComparison.Ordinal) && - !nativePointeeTypeName.StartsWith("union ", StringComparison.Ordinal)) - { - nativePointeeTypeName = nativeNamedTypeName; - isAdjusted = true; - } - } - else if (pointeeType is FunctionType functionType) - { - if (!_config.ExcludeFnptrCodegen && IsType(cursor, functionType, out var functionProtoType)) - { - _config.ExcludeFnptrCodegen = true; - var callConv = GetCallingConvention(cursor, context, rootType); - _config.ExcludeFnptrCodegen = false; - - var needsReturnFixup = false; - var returnTypeName = GetRemappedTypeName(cursor, context: null, functionType.ReturnType, out _, skipUsing: true); - - if (!_config.GenerateDisableRuntimeMarshalling && returnTypeName.Equals("bool", StringComparison.Ordinal)) - { - // bool is not blittable when DisableRuntimeMarshalling is not specified, so we shouldn't use it for P/Invoke signatures - returnTypeName = "byte"; - } - - var nameBuilder = new StringBuilder(); - _ = nameBuilder.Append("delegate"); - _ = nameBuilder.Append('*'); - - var isMacroDefinitionRecord = (cursor is VarDecl varDecl) && GetCursorName(varDecl).StartsWith("ClangSharpMacro_", StringComparison.Ordinal); - - if (!isMacroDefinitionRecord) - { - _ = nameBuilder.Append(" unmanaged"); - var hasSuppressGCTransition = HasSuppressGCTransition(cursor); - - if (callConv != CallConv.Winapi) - { - _ = nameBuilder.Append('['); - _ = nameBuilder.Append(callConv.AsString(true)); - - if (hasSuppressGCTransition) - { - _ = nameBuilder.Append(", SuppressGCTransition"); - } - _ = nameBuilder.Append(']'); - } - else if (hasSuppressGCTransition) - { - _ = nameBuilder.Append("[SuppressGCTransition]"); - } - } - - _ = nameBuilder.Append('<'); - - if ((cursor is CXXMethodDecl cxxMethodDecl) && (context is CXXRecordDecl cxxRecordDecl)) - { - var cxxRecordDeclName = GetRemappedCursorName(cxxRecordDecl); - needsReturnFixup = cxxMethodDecl.IsVirtual && NeedsReturnFixup(cxxMethodDecl); - - _ = nameBuilder.Append(EscapeName(cxxRecordDeclName)); - _ = nameBuilder.Append('*'); - _ = nameBuilder.Append(','); - _ = nameBuilder.Append(' '); - - if (needsReturnFixup) - { - _ = nameBuilder.Append(returnTypeName); - _ = nameBuilder.Append('*'); - _ = nameBuilder.Append(','); - _ = nameBuilder.Append(' '); - } - } - - IEnumerable paramTypes = functionProtoType.ParamTypes; - - if (isMacroDefinitionRecord) - { - Debug.Assert(cursor is not null); - varDecl = (VarDecl)cursor; - - if (IsStmtAsWritten(varDecl.Init, out var declRefExpr, removeParens: true) && (declRefExpr.Decl is FunctionDecl functionDecl)) - { - cursor = functionDecl; - paramTypes = functionDecl.Parameters.Select((param) => param.Type); - returnTypeName = GetRemappedTypeName(cursor, context: null, functionDecl.ReturnType, out _, skipUsing: true); - } - } - - foreach (var paramType in paramTypes) - { - var typeName = GetRemappedTypeName(cursor, context: null, paramType, out _, skipUsing: true); - - if (!_config.GenerateDisableRuntimeMarshalling && typeName.Equals("bool", StringComparison.Ordinal)) - { - // bool is not blittable when DisableRuntimeMarshalling is not specified, so we shouldn't use it for P/Invoke signatures - typeName = "byte"; - } - - _ = nameBuilder.Append(typeName); - _ = nameBuilder.Append(','); - _ = nameBuilder.Append(' '); - } - - if (!needsReturnFixup && ignoreTransparentStructsWhereRequired && _config.WithTransparentStructs.TryGetValue(returnTypeName, out var transparentStruct)) - { - _ = nameBuilder.Append(transparentStruct.Name); - } - else - { - _ = nameBuilder.Append(returnTypeName); - - if (needsReturnFixup) - { - _ = nameBuilder.Append('*'); - } - } - - _ = nameBuilder.Append('>'); - name = nameBuilder.ToString(); - } - else - { - name = "IntPtr"; - } - } - else if (pointeeType is TypedefType typedefType) - { - // We check remapped names here so that types that have variable sizes - // can be treated correctly. Otherwise, they will resolve to a particular - // platform size, based on whatever parameters were passed into clang. - - var remappedName = GetRemappedName(name, cursor, tryRemapOperatorName: false, out var wasRemapped, skipUsing: true); - - if (wasRemapped) - { - name = isTemplate && Config.GenerateGenericPointerWrapper - ? $"Pointer<{remappedName}>" - : $"{remappedName}*"; - } - else - { - name = GetTypeNameForPointeeType(cursor, context, rootType, typedefType.Decl.UnderlyingType, ignoreTransparentStructsWhereRequired, isTemplate, out var nativeUnderlyingTypeName, out isAdjusted); - } - } - else - { - // Otherwise fields that point at anonymous structs get the wrong name - var remappedName = GetRemappedTypeName(cursor, context, pointeeType, out nativePointeeTypeName, skipUsing: true); - - name = isTemplate && Config.GenerateGenericPointerWrapper - ? $"Pointer<{remappedName}>" - : $"{remappedName}*"; - } - - return name; - } - - private void GetTypeSize(Cursor cursor, Type type, ref long alignment32, ref long alignment64, out long size32, out long size64) - { - var has8BytePrimitiveField = false; - GetTypeSize(cursor, type, ref alignment32, ref alignment64, ref has8BytePrimitiveField, out size32, out size64); - } - - private void GetTypeSize(Cursor cursor, Type type, ref long alignment32, ref long alignment64, ref bool has8BytePrimitiveField, out long size32, out long size64) - { - size32 = 0; - size64 = 0; - - // We don't want to handle these using IsType because we need to specially - // handle cases like TypedefType at each level of the type hierarchy - - if (type is ArrayType arrayType) - { - if (IsTypeConstantOrIncompleteArray(cursor, type)) - { - var count = Math.Max((arrayType as ConstantArrayType)?.Size ?? 0, 1); - GetTypeSize(cursor, arrayType.ElementType, ref alignment32, ref alignment64, ref has8BytePrimitiveField, out var elementSize32, out var elementSize64); - - size32 = elementSize32 * Math.Max(count, 1); - size64 = elementSize64 * Math.Max(count, 1); - - if (alignment32 == -1) - { - alignment32 = elementSize32; - } - - if (alignment64 == -1) - { - alignment64 = elementSize64; - } - } - else - { - size32 = 4; - size64 = 8; - - if (alignment32 == -1) - { - alignment32 = 4; - } - - if (alignment64 == -1) - { - alignment64 = 8; - } - } - } - else if (type is AttributedType attributedType) - { - GetTypeSize(cursor, attributedType.ModifiedType, ref alignment32, ref alignment64, ref has8BytePrimitiveField, out size32, out size64); - } - else if (type is BuiltinType) - { - switch (type.Kind) - { - case CXType_Bool: - case CXType_Char_U: - case CXType_UChar: - case CXType_Char_S: - case CXType_SChar: - { - size32 = 1; - size64 = 1; - break; - } - - case CXType_UShort: - case CXType_Short: - { - size32 = 2; - size64 = 2; - break; - } - - case CXType_UInt: - case CXType_Int: - case CXType_Float: - { - size32 = 4; - size64 = 4; - break; - } - - case CXType_ULong: - case CXType_Long: - { - if (_config.GenerateUnixTypes) - { - size32 = 4; - size64 = 8; - - if (alignment32 == -1) - { - alignment32 = 4; - } - - if (alignment64 == -1) - { - alignment64 = 8; - } - } - else - { - goto case CXType_UInt; - } - break; - } - - case CXType_ULongLong: - case CXType_LongLong: - case CXType_Double: - { - size32 = 8; - size64 = 8; - - if (alignment32 == -1) - { - alignment32 = 8; - } - - if (alignment64 == -1) - { - alignment64 = 8; - } - - has8BytePrimitiveField = true; - break; - } - - case CXType_WChar: - { - if (_config.GenerateUnixTypes) - { - goto case CXType_Int; - } - else - { - goto case CXType_UShort; - } - } - - default: - { - AddDiagnostic(DiagnosticLevel.Error, $"Unsupported builtin type: '{type.KindSpelling}.", cursor); - break; - } - } - } - else if (type is DecltypeType decltypeType) - { - GetTypeSize(cursor, decltypeType.UnderlyingType, ref alignment32, ref alignment64, ref has8BytePrimitiveField, out size32, out size64); - } - else if (type is ElaboratedType elaboratedType) - { - GetTypeSize(cursor, elaboratedType.NamedType, ref alignment32, ref alignment64, ref has8BytePrimitiveField, out size32, out size64); - } - else if (type is EnumType enumType) - { - GetTypeSize(cursor, enumType.Decl.IntegerType, ref alignment32, ref alignment64, ref has8BytePrimitiveField, out size32, out size64); - } - else if (type is FunctionType or PointerType or ReferenceType) - { - size32 = 4; - size64 = 8; - - if (alignment32 == -1) - { - alignment32 = 4; - } - - if (alignment64 == -1) - { - alignment64 = 8; - } - } - else if (type is InjectedClassNameType) - { - // Nothing to handle - } - else if (type is RecordType recordType) - { - var recordTypeAlignOf = Math.Min(recordType.Handle.AlignOf, 8); - - if (alignment32 == -1) - { - alignment32 = recordTypeAlignOf; - } - - if (alignment64 == -1) - { - alignment64 = recordTypeAlignOf; - } - - long maxFieldAlignment32 = -1; - long maxFieldAlignment64 = -1; - - long maxFieldSize32 = 0; - long maxFieldSize64 = 0; - - var anyFieldIs8BytePrimitive = false; - - if (recordType.Decl is CXXRecordDecl cxxRecordDecl) - { - if (HasVtbl(cxxRecordDecl, out _)) - { - size32 += 4; - size64 += 8; - - if (alignment32 < 4) - { - alignment32 = Math.Max(Math.Min(alignment32, 4), 1); - } - - if (alignment64 < 4) - { - alignment64 = Math.Max(Math.Min(alignment32, 8), 1); - } - - maxFieldSize32 = Math.Max(maxFieldSize32, 4); - maxFieldSize64 = Math.Max(maxFieldSize64, 8); - - maxFieldAlignment32 = Math.Max(maxFieldSize32, 4); - maxFieldAlignment64 = Math.Max(maxFieldSize64, 8); - } - else - { - foreach (var baseCXXRecordDecl in cxxRecordDecl.Bases) - { - long fieldAlignment32 = -1; - long fieldAlignment64 = -1; - - GetTypeSize(baseCXXRecordDecl, baseCXXRecordDecl.Type, ref fieldAlignment32, ref fieldAlignment64, ref anyFieldIs8BytePrimitive, out var fieldSize32, out var fieldSize64); - - if ((fieldAlignment32 == -1) || (alignment32 < 4)) - { - fieldAlignment32 = Math.Max(Math.Min(alignment32, fieldSize32), 1); - } - - if ((fieldAlignment64 == -1) || (alignment64 < 4)) - { - fieldAlignment64 = Math.Max(Math.Min(alignment64, fieldSize64), 1); - } - - if ((size32 % fieldAlignment32) != 0) - { - size32 += fieldAlignment32 - (size32 % fieldAlignment32); - } - - if ((size64 % fieldAlignment64) != 0) - { - size64 += fieldAlignment64 - (size64 % fieldAlignment64); - } - - size32 += fieldSize32; - size64 += fieldSize64; - - maxFieldAlignment32 = Math.Max(maxFieldAlignment32, fieldAlignment32); - maxFieldAlignment64 = Math.Max(maxFieldAlignment64, fieldAlignment64); - - maxFieldSize32 = Math.Max(maxFieldSize32, fieldSize32); - maxFieldSize64 = Math.Max(maxFieldSize64, fieldSize64); - } - } - } - - var bitfieldPreviousSize32 = 0L; - var bitfieldPreviousSize64 = 0L; - var bitfieldRemainingBits32 = 0L; - var bitfieldRemainingBits64 = 0L; - - foreach (var fieldDecl in recordType.Decl.Fields) - { - long fieldAlignment32 = -1; - long fieldAlignment64 = -1; - - GetTypeSize(fieldDecl, fieldDecl.Type, ref fieldAlignment32, ref fieldAlignment64, ref anyFieldIs8BytePrimitive, out var fieldSize32, out var fieldSize64); - - var ignoreFieldSize32 = false; - var ignoreFieldSize64 = false; - - if (fieldDecl.IsBitField) - { - if (fieldSize32 != bitfieldPreviousSize32) - { - bitfieldRemainingBits32 = fieldSize32 * 8; - bitfieldPreviousSize32 = fieldSize32; - bitfieldRemainingBits32 -= fieldDecl.BitWidthValue; - } - else if (fieldDecl.BitWidthValue > bitfieldRemainingBits32) - { - if (bitfieldRemainingBits32 != bitfieldRemainingBits64) - { - ignoreFieldSize32 = true; - } - - bitfieldRemainingBits32 = fieldSize32 * 8; - bitfieldPreviousSize32 = fieldSize32; - bitfieldRemainingBits32 -= fieldDecl.BitWidthValue; - } - else - { - bitfieldPreviousSize32 = fieldSize32; - bitfieldRemainingBits32 -= fieldDecl.BitWidthValue; - ignoreFieldSize32 = true; - } - - if ((fieldSize64 != bitfieldPreviousSize64) || (fieldDecl.BitWidthValue > bitfieldRemainingBits64)) - { - bitfieldRemainingBits64 = fieldSize64 * 8; - bitfieldPreviousSize64 = fieldSize64; - bitfieldRemainingBits64 -= fieldDecl.BitWidthValue; - } - else - { - bitfieldPreviousSize64 = fieldSize64; - bitfieldRemainingBits64 -= fieldDecl.BitWidthValue; - ignoreFieldSize64 = true; - } - } - - if (!ignoreFieldSize32) - { - if ((fieldAlignment32 == -1) || (alignment32 < 4)) - { - fieldAlignment32 = Math.Max(Math.Min(alignment32, fieldSize32), 1); - } - - if ((size32 % fieldAlignment32) != 0) - { - size32 += fieldAlignment32 - (size32 % fieldAlignment32); - } - - size32 += fieldSize32; - maxFieldAlignment32 = Math.Max(maxFieldAlignment32, fieldAlignment32); - maxFieldSize32 = Math.Max(maxFieldSize32, fieldSize32); - } - - if (!ignoreFieldSize64) - { - if ((fieldAlignment64 == -1) || (alignment64 < 4)) - { - fieldAlignment64 = Math.Max(Math.Min(alignment64, fieldSize64), 1); - } - - if ((size64 % fieldAlignment64) != 0) - { - size64 += fieldAlignment64 - (size64 % fieldAlignment64); - } - - size64 += fieldSize64; - maxFieldAlignment64 = Math.Max(maxFieldAlignment64, fieldAlignment64); - maxFieldSize64 = Math.Max(maxFieldSize64, fieldSize64); - } - } - - if ((alignment32 == 8) && !anyFieldIs8BytePrimitive) - { - alignment32 = Math.Min(alignment32, maxFieldAlignment32); - } - - if ((alignment64 == 4) && !anyFieldIs8BytePrimitive) - { - alignment64 = Math.Max(alignment64, maxFieldAlignment64); - } - - if (recordType.Decl.IsUnion) - { - size32 = maxFieldSize32; - size64 = maxFieldSize64; - } - - if ((size32 % alignment32) != 0) - { - size32 += alignment32 - (size32 % alignment32); - } - - if ((size64 % alignment64) != 0) - { - size64 += alignment64 - (size64 % alignment64); - } - - has8BytePrimitiveField |= anyFieldIs8BytePrimitive; - } - else if (type is TypedefType typedefType) - { - // We check remapped names here so that types that have variable sizes - // can be treated correctly. Otherwise, they will resolve to a particular - // platform size, based on whatever parameters were passed into clang. - - var name = GetTypeName(cursor, context: null, type: type, ignoreTransparentStructsWhereRequired: false, isTemplate: false, nativeTypeName: out _); - var remappedName = GetRemappedTypeName(cursor, context: null, type, out _, skipUsing: true, ignoreTransparentStructsWhereRequired: false); - - if ((remappedName == name) && _config.WithTransparentStructs.TryGetValue(remappedName, out var transparentStruct) && (transparentStruct.Name.Equals("long", StringComparison.Ordinal) || transparentStruct.Name.Equals("ulong", StringComparison.Ordinal))) - { - size32 = 8; - size64 = 8; - - if (alignment32 == -1) - { - alignment32 = 8; - } - - if (alignment64 == -1) - { - alignment64 = 8; - } - - has8BytePrimitiveField = true; - } - else if (remappedName.Equals("IntPtr", StringComparison.Ordinal) || - remappedName.Equals("nint", StringComparison.Ordinal) || - remappedName.Equals("nuint", StringComparison.Ordinal) || - remappedName.Equals("UIntPtr", StringComparison.Ordinal) || - remappedName.EndsWith('*')) - { - size32 = 4; - size64 = 8; - - if (alignment32 == -1) - { - alignment32 = 4; - } - - if (alignment64 == -1) - { - alignment64 = 8; - } - } - else - { - GetTypeSize(cursor, typedefType.Decl.UnderlyingType, ref alignment32, ref alignment64, ref has8BytePrimitiveField, out size32, out size64); - } - } - else if (type is SubstTemplateTypeParmType substTemplateTypeParmType) - { - GetTypeSize(cursor, substTemplateTypeParmType.ReplacementType, ref alignment32, ref alignment64, ref has8BytePrimitiveField, out size32, out size64); - } - else if (type is TemplateSpecializationType templateSpecializationType) - { - if (templateSpecializationType.IsTypeAlias) - { - GetTypeSize(cursor, templateSpecializationType.AliasedType, ref alignment32, ref alignment64, ref has8BytePrimitiveField, out size32, out size64); - } - else if (templateSpecializationType.IsSugared) - { - GetTypeSize(cursor, templateSpecializationType.Desugar, ref alignment32, ref alignment64, ref has8BytePrimitiveField, out size32, out size64); - } - else if (templateSpecializationType.TemplateName.AsTemplateDecl is TemplateDecl templateDecl) - { - if (templateDecl.TemplatedDecl is TypeDecl typeDecl) - { - GetTypeSize(cursor, typeDecl.TypeForDecl, ref alignment32, ref alignment64, ref has8BytePrimitiveField, out size32, out size64); - } - else - { - AddDiagnostic(DiagnosticLevel.Error, $"Unsupported template specialization declaration kind: '{templateDecl.TemplatedDecl.DeclKindName}'.", cursor); - } - } - else - { - AddDiagnostic(DiagnosticLevel.Error, $"Unsupported template specialization type: '{templateSpecializationType}'.", cursor); - } - } - else if (type is TemplateTypeParmType) - { - // Nothing to handle - } - else - { - AddDiagnostic(DiagnosticLevel.Error, $"Unsupported type: '{type.TypeClass}'.", cursor); - } - } - - private bool HasSuppressGCTransition(Cursor? cursor) - => (cursor is NamedDecl namedDecl) && HasRemapping(namedDecl, _config._withSuppressGCTransitions); - - private bool HasBaseField(CXXRecordDecl cxxRecordDecl) - { - var hasBaseField = false; - - foreach (var cxxBaseSpecifier in cxxRecordDecl.Bases) - { - var baseCxxRecordDecl = GetRecordDecl(cxxBaseSpecifier); - - if (HasField(baseCxxRecordDecl)) - { - hasBaseField = true; - break; - } - } - - return hasBaseField; - } - - private bool HasField(RecordDecl recordDecl) - { - var hasField = recordDecl.Fields.Any() || recordDecl.Decls.Any((decl) => (decl is RecordDecl nestedRecordDecl) && nestedRecordDecl.IsAnonymousStructOrUnion && HasField(nestedRecordDecl)); - - if (!hasField && (recordDecl is CXXRecordDecl cxxRecordDecl)) - { - hasField = HasBaseField(cxxRecordDecl); - } - - return hasField; - } - - private bool HasUnsafeMethod(CXXRecordDecl cxxRecordDecl) - { - var hasUnsafeMethod = cxxRecordDecl.Methods.Any((method) => method.IsUserProvided && IsUnsafe(method) && !IsExcluded(method)); - - if (!hasUnsafeMethod) - { - foreach (var cxxBaseSpecifier in cxxRecordDecl.Bases) - { - var baseCxxRecordDecl = GetRecordDecl(cxxBaseSpecifier); - - if (HasUnsafeMethod(baseCxxRecordDecl)) - { - hasUnsafeMethod = true; - break; - } - } - } - - return hasUnsafeMethod; - } - - private bool HasVtbl(CXXRecordDecl cxxRecordDecl, out bool hasBaseVtbl) - { - var hasVtbl = cxxRecordDecl.Methods.Any((method) => method.IsVirtual && method.IsVirtual && (method.OverriddenMethods.Count == 0)); - hasBaseVtbl = false; - - if (!hasVtbl) - { - var indirectVtblCount = 0; - - foreach (var cxxBaseSpecifier in cxxRecordDecl.Bases) - { - var baseCxxRecordDecl = GetRecordDecl(cxxBaseSpecifier); - - if ((HasVtbl(baseCxxRecordDecl, out var baseHasBaseVtbl) || baseHasBaseVtbl) && !HasField(baseCxxRecordDecl)) - { - indirectVtblCount++; - } - } - - if (indirectVtblCount > 1) - { - AddDiagnostic(DiagnosticLevel.Warning, "Unsupported cxx record declaration: 'multiple virtual bases'. Generated bindings may be incomplete.", cxxRecordDecl); - } - - hasBaseVtbl = indirectVtblCount != 0; - } - - return hasVtbl; - } - - private static bool IsEnumOperator(FunctionDecl functionDecl, string name) - { - if (name.StartsWith("operator", StringComparison.Ordinal) && ((functionDecl.Parameters.Count == 1) || (functionDecl.Parameters.Count == 2))) - { - var parmVarDecl1 = functionDecl.Parameters[0]; - var parmVarDecl1Type = parmVarDecl1.Type; - - if (IsType(parmVarDecl1, parmVarDecl1Type, out var pointerType1)) - { - parmVarDecl1Type = pointerType1.PointeeType; - } - else if (IsType(parmVarDecl1, parmVarDecl1Type, out var referenceType1)) - { - parmVarDecl1Type = referenceType1.PointeeType; - } - - if (functionDecl.Parameters.Count == 1) - { - return IsType(parmVarDecl1); - } - - var parmVarDecl2 = functionDecl.Parameters[1]; - var parmVarDecl2Type = parmVarDecl2.Type; - - if (IsType(parmVarDecl2, parmVarDecl2Type, out var pointerType2)) - { - parmVarDecl2Type = pointerType2.PointeeType; - } - else if (IsType(parmVarDecl2, parmVarDecl2Type, out var referenceType2)) - { - parmVarDecl2Type = referenceType2.PointeeType; - } - - if ((parmVarDecl1Type.CanonicalType == parmVarDecl2Type.CanonicalType) && IsType(parmVarDecl2)) - { - return true; - } - } - return false; - } - - private bool IsExcluded(Cursor cursor) => IsExcluded(cursor, out _); - - private bool IsExcluded(Cursor cursor, out bool isExcludedByConflictingDefinition) - { - if (!_isExcluded.TryGetValue(cursor, out var isExcludedValue)) - { - isExcludedValue |= (!IsAlwaysIncluded(cursor) && (IsExcludedByConfig(cursor) || IsExcludedByFile(cursor) || IsExcludedByName(cursor, ref isExcludedValue) || IsExcludedByAttributes(cursor))) ? 0b01u : 0b00u; - _isExcluded.Add(cursor, isExcludedValue); - } - isExcludedByConflictingDefinition = (isExcludedValue & 0b10) != 0; - return (isExcludedValue & 0b01) != 0; - - bool IsAlwaysIncluded(Cursor cursor) - { - return (cursor is TranslationUnitDecl) || (cursor is LinkageSpecDecl) || (cursor is NamespaceDecl) || ((cursor is VarDecl varDecl) && varDecl.Name.StartsWith("ClangSharpMacro_", StringComparison.Ordinal)); - } - - bool IsExcludedByConfig(Cursor cursor) - { - return (_config.ExcludeFunctionsWithBody && (cursor is FunctionDecl functionDecl) && functionDecl.HasBody) - || (!_config.GenerateTemplateBindings && ((cursor is TemplateDecl) || (cursor is ClassTemplateSpecializationDecl))); - } - - bool IsExcludedByFile(Cursor cursor) - { - if (_outputBuilder != null) - { - // We don't want to exclude by file if we already have an active output builder as we - // are likely processing members of an already included type but those members may - // indirectly exist or be defined in a non-traversed file. - return false; - } - - var declLocation = cursor.Location; - declLocation.GetFileLocation(out var file, out var line, out var column, out _); - - if (IsIncludedFileOrLocation(cursor, file, declLocation)) - { - return false; - } - - // It is not uncommon for some declarations to be done using macros, which are themselves - // defined in an imported header file. We want to also check if the expansion location is - // in the main file to catch these cases and ensure we still generate bindings for them. - - declLocation.GetExpansionLocation(out var expansionFile, out var expansionLine, out var expansionColumn, out _); - - if ((expansionFile == file) && (expansionLine == line) && (expansionColumn == column) && _config.TraversalNames.Count != 0) - { - // clang_getLocation is a very expensive call, so exit early if the expansion file is the same - // However, if we are not explicitly specifying traversal names, its possible the expansion location - // is the same, but IsMainFile is now marked as true, in which case we can't exit early. - - return true; - } - - var expansionLocation = cursor.TranslationUnit.Handle.GetLocation(expansionFile, expansionLine, expansionColumn); - - return !IsIncludedFileOrLocation(cursor, file, expansionLocation); - } - - bool IsExcludedByName(Cursor cursor, ref uint isExcludedValue) - { - var isExcludedByConfigOption = false; - var qualifiedNameWithoutParameters = ""; - - string qualifiedName; - string name; - string kind; - - if (cursor is NamedDecl namedDecl) - { - // We get the non-remapped name for the purpose of exclusion checks to ensure that users - // can remove no-definition declarations in favor of remapped anonymous declarations. - - qualifiedName = GetCursorQualifiedName(namedDecl); - - if (namedDecl is FunctionDecl) - { - qualifiedNameWithoutParameters = GetCursorQualifiedName(namedDecl, truncateParameters: true); - } - - name = GetCursorName(namedDecl); - kind = $"{namedDecl.DeclKindName} declaration"; - - if ((namedDecl is TagDecl tagDecl) && (tagDecl.Definition != tagDecl) && (tagDecl.Definition != null)) - { - // We don't want to generate bindings for anything - // that is not itself a definition and that has a - // definition that can be resolved. This ensures we - // still generate bindings for things which are used - // as opaque handles, but which aren't ever defined. - - if (_config.LogExclusions) - { - AddDiagnostic(DiagnosticLevel.Info, $"Excluded {kind} '{qualifiedName}' by as it is not a definition."); - } - return true; - } - } - else if (cursor is MacroDefinitionRecord macroDefinitionRecord) - { - qualifiedName = macroDefinitionRecord.Name; - name = macroDefinitionRecord.Name; - kind = macroDefinitionRecord.CursorKindSpelling; - } - else - { - return false; - } - - if (qualifiedName.Contains("ClangSharpMacro_", StringComparison.Ordinal)) - { - qualifiedName = qualifiedName.Replace("ClangSharpMacro_", "", StringComparison.Ordinal); - } - - if (name.Contains("ClangSharpMacro_", StringComparison.Ordinal)) - { - name = name.Replace("ClangSharpMacro_", "", StringComparison.Ordinal); - } - - if (cursor is RecordDecl recordDecl) - { - if (_config.ExcludeEmptyRecords && IsEmptyRecord(recordDecl)) - { - isExcludedByConfigOption = true; - } - } - else if (cursor is FunctionDecl functionDecl) - { - if (_config.ExcludeComProxies && IsComProxy(functionDecl, name)) - { - isExcludedByConfigOption = true; - } - else if (_config.ExcludeEnumOperators && IsEnumOperator(functionDecl, name)) - { - isExcludedByConfigOption = true; - } - else if (functionDecl is CXXMethodDecl cxxMethodDecl) - { - var parent = cxxMethodDecl.Parent; - Debug.Assert(parent is not null); - - if (IsConflictingMethodDecl(cxxMethodDecl, parent)) - { - isExcludedValue |= 0b10; - } - } - - if (_config.GenerateDisableRuntimeMarshalling && functionDecl.IsVariadic) - { - isExcludedByConfigOption = true; - } - } - - if (_config.ExcludedNames.Contains(qualifiedName)) - { - if (_config.LogExclusions) - { - var message = $"Excluded {kind} '{qualifiedName}' by exact match"; - - if (isExcludedByConfigOption) - { - message += "; Exclusion is unnecessary due to a config option"; - } - else if ((isExcludedValue & 0b10) != 0) - { - message += "; Exclusion is unnecessary due to a conflicting definition"; - } - - AddDiagnostic(DiagnosticLevel.Info, message); - } - return true; - } - - if (_config.ExcludedNames.Contains(qualifiedNameWithoutParameters) || _config.ExcludedNames.Contains(name)) - { - if (_config.LogExclusions) - { - var message = $"Excluded {kind} '{qualifiedName}' by partial match against {name}"; - - if (isExcludedByConfigOption) - { - message += "; Exclusion is unnecessary due to a config option"; - } - else if ((isExcludedValue & 0b10) != 0) - { - message += "; Exclusion is unnecessary due to a conflicting definition"; - } - - AddDiagnostic(DiagnosticLevel.Info, message); - } - return true; - } - - if (isExcludedByConfigOption) - { - if (_config.LogExclusions) - { - AddDiagnostic(DiagnosticLevel.Info, $"Excluded {kind} '{qualifiedName}' by config option"); - } - return true; - } - - if (_config.IncludedNames.Count != 0 && !_config.IncludedNames.Contains(qualifiedName) - && !_config.IncludedNames.Contains(qualifiedNameWithoutParameters) - && !_config.IncludedNames.Contains(name)) - { - var semanticParentCursor = cursor.SemanticParentCursor; - - if ((semanticParentCursor is null) || IsExcluded(semanticParentCursor) || IsAlwaysIncluded(semanticParentCursor)) - { - if (_config.LogExclusions) - { - AddDiagnostic(DiagnosticLevel.Info, $"Excluded {kind} '{qualifiedName}' as it was not in the include list"); - } - return true; - } - } - - if ((isExcludedValue & 0b10) != 0) - { - if (_config.LogExclusions) - { - AddDiagnostic(DiagnosticLevel.Info, $"Excluded {kind} '{qualifiedName}' by conflicting definition"); - } - return true; - } - - return false; - } - - bool IsIncludedFileOrLocation(Cursor cursor, CXFile file, CXSourceLocation location) - { - // Use case insensitive comparison on Windows - var equalityComparer = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal; - - // Normalize paths to be '/' for comparison - var fileName = file.Name.ToString().NormalizePath(); - - if (_visitedFiles.Add(fileName) && _config.LogVisitedFiles) - { - AddDiagnostic(DiagnosticLevel.Info, $"Visiting {fileName}"); - } - - if (_config.TraversalNames.Contains(fileName, equalityComparer)) - { - return true; - } - else if (_config.TraversalNames.Contains(fileName.NormalizeFullPath(), equalityComparer)) - { - return true; - } - else if (_config.TraversalNames.Count == 0 && location.IsFromMainFile) - { - return true; - } - - return false; - } - - bool IsComProxy(FunctionDecl functionDecl, string name) - { - var parmVarDecl = null as ParmVarDecl; - - if (name.EndsWith("_UserFree", StringComparison.Ordinal) || name.EndsWith("_UserFree64", StringComparison.Ordinal) || - name.EndsWith("_UserMarshal", StringComparison.Ordinal) || name.EndsWith("_UserMarshal64", StringComparison.Ordinal) || - name.EndsWith("_UserSize", StringComparison.Ordinal) || name.EndsWith("_UserSize64", StringComparison.Ordinal) || - name.EndsWith("_UserUnmarshal", StringComparison.Ordinal) || name.EndsWith("_UserUnmarshal64", StringComparison.Ordinal)) - { - var parameters = functionDecl.Parameters; - parmVarDecl = (parameters.Count != 0) ? parameters[^1] : null; - } - else if (name.EndsWith("_Proxy", StringComparison.Ordinal) || name.EndsWith("_Stub", StringComparison.Ordinal)) - { - var parameters = functionDecl.Parameters; - parmVarDecl = (parameters.Count != 0) ? parameters[0] : null; - } - - if ((parmVarDecl is not null) && IsType(parmVarDecl, out var pointerType)) - { - var typeName = GetTypeName(parmVarDecl, context: null, type: pointerType.PointeeType, ignoreTransparentStructsWhereRequired: false, isTemplate: false, nativeTypeName: out var nativeTypeName); - return name.StartsWith($"{nativeTypeName}_", StringComparison.Ordinal) || name.StartsWith($"{typeName}_", StringComparison.Ordinal) || typeName.Equals("IRpcStubBuffer", StringComparison.Ordinal); - } - return false; - } - - bool IsConflictingMethodDecl(CXXMethodDecl cxxMethodDeclToMatch, CXXRecordDecl cxxRecordDecl) - { - var cxxMethodDeclToMatchName = GetRemappedCursorName(cxxMethodDeclToMatch); - var foundCxxMethodDeclToMatch = false; - - foreach (var cxxBaseSpecifier in cxxRecordDecl.Bases) - { - var baseCxxRecordDecl = GetRecordDecl(cxxBaseSpecifier); - - if (ContainsConflictingMethodDecl(cxxMethodDeclToMatch, cxxRecordDecl, baseCxxRecordDecl, cxxMethodDeclToMatchName, ref foundCxxMethodDeclToMatch)) - { - return true; - } - } - - return ContainsConflictingMethodDecl(cxxMethodDeclToMatch, cxxRecordDecl, cxxRecordDecl, cxxMethodDeclToMatchName, ref foundCxxMethodDeclToMatch); - - bool ContainsConflictingMethodDecl(CXXMethodDecl cxxMethodDeclToMatch, CXXRecordDecl rootCxxRecordDecl, CXXRecordDecl cxxRecordDecl, string cxxMethodDeclToMatchName, ref bool foundCxxMethodDeclToMatch) - { - var cxxMethodDecls = cxxRecordDecl.Methods; - - if (cxxMethodDecls.Count != 0) - { - foreach (var cxxMethodDecl in cxxMethodDecls.OrderBy((cxxmd) => cxxmd.VtblIndex)) - { - if (IsConflictingMethodDecl(cxxMethodDeclToMatch, cxxMethodDecl, rootCxxRecordDecl, cxxRecordDecl, cxxMethodDeclToMatchName, ref foundCxxMethodDeclToMatch)) - { - return true; - } - } - } - - return false; - } - - bool IsConflictingMethodDecl(CXXMethodDecl cxxMethodDeclToMatch, CXXMethodDecl cxxMethodDecl, CXXRecordDecl rootCxxRecordDecl, CXXRecordDecl cxxRecordDecl, string cxxMethodDeclToMatchName, ref bool foundCxxMethodDeclToMatch) - { - var methodName = GetRemappedCursorName(cxxMethodDecl); - - if (cxxMethodDeclToMatchName != methodName) - { - return false; - } - - if (cxxMethodDecl == cxxMethodDeclToMatch) - { - foundCxxMethodDeclToMatch = true; - return false; - } - - if (cxxMethodDecl.Parameters.Count != cxxMethodDeclToMatch.Parameters.Count) - { - return false; - } - - var allMatch = true; - - for (var n = 0; n < cxxMethodDeclToMatch.Parameters.Count; n++) - { - var parameterTypeToMatch = cxxMethodDeclToMatch.Parameters[n].Type; - var parameterType = cxxMethodDecl.Parameters[n].Type; - - if (parameterType.CanonicalType == parameterTypeToMatch.CanonicalType) - { - continue; - } - - if (IsType(cursor, parameterTypeToMatch, out var pointerTypeToMatch) && - IsType(cursor, parameterType, out var referenceType) && - (referenceType.PointeeType.CanonicalType == pointerTypeToMatch.PointeeType.CanonicalType)) - { - continue; - } - - if (IsType(cursor, parameterTypeToMatch, out var referenceTypeToMatch) && - IsType(cursor, parameterType, out var pointerType) && - (pointerType.PointeeType.CanonicalType == referenceTypeToMatch.PointeeType.CanonicalType)) - { - continue; - } - - allMatch = false; - break; - } - - if (!allMatch) - { - return false; - } - - if (cxxMethodDecl.IsVirtual) - { - if (cxxMethodDeclToMatch.IsVirtual) - { - if (rootCxxRecordDecl != cxxRecordDecl) - { - // The found declaration and declaration to match are both virtual - // We want to treat the one from the base declaration as non-conflicting - // So return true to report the declaration to match as the conflict - return true; - } - else if (cxxMethodDeclToMatch.IsThisDeclarationADefinition != cxxMethodDecl.IsThisDeclarationADefinition) - { - return false; - } - else - { - AddDiagnostic(DiagnosticLevel.Error, "Found conflicting method definitions for two virtual methods.", cxxMethodDeclToMatch); - } - } - else - { - // The found declaration is virtual while the declaration to match is not - // We want to treat the virtual declaration as non-conflicting - // So return true to report the declaration to match as the conflict - return true; - } - } - else if (cxxMethodDeclToMatch.IsVirtual) - { - // The declaration to match is virtual while the found declaration is not - // We want to treat the virtual declaration as non-conflicting - // So treat the declaration as non-conflicting and continue searching - return false; - } - else - { - // Neither the declaration nor the declaration to match are virtual - // We want to pick whichever declaration appears first - // So return true or false based on if we already encountered the declaration to match - return !foundCxxMethodDeclToMatch; - } - - return false; - } - } - - bool IsEmptyRecord(RecordDecl recordDecl) - { - if (recordDecl.Fields.Count != 0) - { - if (!GetCursorName(recordDecl).EndsWith("__", StringComparison.Ordinal) || (recordDecl.Fields.Count != 1)) - { - return false; - } - - var field = recordDecl.Fields[0]; - - if (!GetCursorName(field).Equals("unused", StringComparison.Ordinal) || !IsType(field, out var builtinType) || (builtinType.Kind != CXType_Int)) - { - return false; - } - } - - foreach (var decl in recordDecl.Decls) - { - if ((decl is RecordDecl nestedRecordDecl) && nestedRecordDecl.IsAnonymousStructOrUnion && !IsEmptyRecord(nestedRecordDecl)) - { - return false; - } - - if ((decl is CXXMethodDecl cxxMethodDecl) && cxxMethodDecl.IsVirtual) - { - return false; - } - } - - if (recordDecl is CXXRecordDecl cxxRecordDecl) - { - foreach (var cxxBaseSpecifier in cxxRecordDecl.Bases) - { - var baseCxxRecordDecl = GetRecordDecl(cxxBaseSpecifier); - - if (!IsEmptyRecord(baseCxxRecordDecl)) - { - return false; - } - } - } - - return !TryGetUuid(recordDecl, out _); - } - - bool IsExcludedByAttributes(Cursor cursor) - { - if (cursor is NamedDecl namedDecl) - { - foreach (var attr in GetAttributesFor(namedDecl)) - { - switch (attr.Kind) - { - case CX_AttrKind_Builtin: - return true; - } - } - } - - return false; - } - } - - private bool IsBaseExcluded(CXXRecordDecl cxxRecordDecl, CXXRecordDecl baseCxxRecordDecl, CXXBaseSpecifier cxxBaseSpecifier, out string baseFieldName) - { - baseFieldName = GetAnonymousName(cxxBaseSpecifier, "Base"); - baseFieldName = GetRemappedName(baseFieldName, cxxBaseSpecifier, tryRemapOperatorName: true, out _, skipUsing: true); - - var qualifiedName = $"{GetCursorQualifiedName(cxxRecordDecl)}::{baseFieldName}"; - return _config.ExcludedNames.Contains(qualifiedName); - } - - private bool IsFixedSize(Cursor cursor, Type type) - { - // We don't want to handle these using IsType because we need to specially - // handle cases like TypedefType at each level of the type hierarchy - - if (type is ArrayType) - { - return false; - } - else if (type is AttributedType attributedType) - { - return IsFixedSize(cursor, attributedType.ModifiedType); - } - else if (type is BuiltinType) - { - return true; - } - else if (type is DecltypeType decltypeType) - { - return IsFixedSize(cursor, decltypeType.UnderlyingType); - } - else if (type is ElaboratedType elaboratedType) - { - return IsFixedSize(cursor, elaboratedType.NamedType); - } - else if (type is EnumType enumType) - { - return IsFixedSize(cursor, enumType.Decl.IntegerType); - } - else if (type is FunctionType) - { - return false; - } - else if (type is PointerType) - { - return false; - } - else if (type is RecordType recordType) - { - var recordDecl = recordType.Decl; - - return recordDecl.Fields.All((fieldDecl) => IsFixedSize(fieldDecl, fieldDecl.Type)) - && (recordDecl is not CXXRecordDecl cxxRecordDecl || cxxRecordDecl.Methods.All((cxxMethodDecl) => !cxxMethodDecl.IsVirtual)); - } - else if (type is ReferenceType) - { - return false; - } - else if (type is TypedefType typedefType) - { - var name = GetTypeName(cursor, context: null, type: type, ignoreTransparentStructsWhereRequired: false, isTemplate: false, nativeTypeName: out _); - var remappedName = GetRemappedTypeName(cursor, context: null, type, out _, skipUsing: true, ignoreTransparentStructsWhereRequired: false); - - return !remappedName.Equals("IntPtr", StringComparison.Ordinal) - && !remappedName.Equals("nint", StringComparison.Ordinal) - && !remappedName.Equals("nuint", StringComparison.Ordinal) - && !remappedName.Equals("UIntPtr", StringComparison.Ordinal) - && IsFixedSize(cursor, typedefType.Decl.UnderlyingType); - } - else - { - AddDiagnostic(DiagnosticLevel.Warning, $"Unsupported type: '{type.TypeClass}'. Assuming unfixed size.", cursor); - return false; - } - } - - private static bool IsNativeTypeNameEquivalent(string nativeTypeName, string typeName) - { - return nativeTypeName.Equals(typeName, StringComparison.OrdinalIgnoreCase) - || nativeTypeName.Replace(" ", "", StringComparison.Ordinal).Equals(typeName, StringComparison.OrdinalIgnoreCase); - } - - private bool IsPrevContextDecl([MaybeNullWhen(false)] out T cursor, out object? userData, bool includeLast = false) - where T : Decl - { - var previousContext = _context.Last; - Debug.Assert(previousContext != null); - - if (!includeLast) - { - previousContext = previousContext.Previous; - Debug.Assert(previousContext != null); - } - - while (previousContext.Value.Cursor is not Decl) - { - previousContext = previousContext.Previous; - Debug.Assert(previousContext != null); - } - - var value = previousContext.Value; - - if (value.Cursor is T t) - { - cursor = t; - userData = value.UserData; - return true; - } - else - { - cursor = null; - userData = null; - return false; - } - } - - private bool IsPrevContextStmt([MaybeNullWhen(false)] out T cursor, out object? userData, bool preserveParen = false, bool preserveImplicitCast = false) - where T : Stmt - { - var previousContext = _context.Last; - Debug.Assert(previousContext != null); - - do - { - previousContext = previousContext.Previous; - Debug.Assert(previousContext is not null); - } - while ((!preserveParen && (previousContext.Value.Cursor is ParenExpr)) || (!preserveImplicitCast && (previousContext.Value.Cursor is ImplicitCastExpr))); - - var value = previousContext.Value; - - if (value.Cursor is T t) - { - cursor = t; - userData = value.UserData; - return true; - } - else - { - cursor = null; - userData = null; - return false; - } - } - - private bool IsReadonly(CXXMethodDecl? cxxMethodDecl) - { - if (cxxMethodDecl is not null) - { - return cxxMethodDecl.IsConst || HasRemapping(cxxMethodDecl, _config._withReadonlys, matchStar: true); - } - return false; - } - - private static bool IsStmtAsWritten(Cursor cursor, [MaybeNullWhen(false)] out T value, bool removeParens = false) - where T : Stmt - { - if (cursor is Expr expr) - { - cursor = GetExprAsWritten(expr, removeParens); - } - - if (cursor is T t) - { - value = t; - return true; - } - else - { - value = null; - return false; - } - } - - private static bool IsStmtAsWritten(Stmt stmt, Stmt expectedStmt, bool removeParens = false) - { - if (stmt == expectedStmt) - { - return true; - } - - if (stmt is not Expr expr) - { - return false; - } - - expr = GetExprAsWritten(expr, removeParens); - return expr == expectedStmt; - } - - private static bool IsType(Expr expr) - where T : Type => IsType(expr, out _); - - private static bool IsType(Expr expr, [MaybeNullWhen(false)] out T value) - where T : Type => IsType(expr, expr.Type, out value); - - private static bool IsType(ValueDecl valueDecl) - where T : Type => IsType(valueDecl, out _); - - private static bool IsType(ValueDecl typeDecl, [MaybeNullWhen(false)] out T value) - where T : Type => IsType(typeDecl, typeDecl.Type, out value); - - private static bool IsType(Cursor? cursor, Type type) - where T : Type => IsType(cursor, type, out _); - - private static bool IsType(Cursor? cursor, Type type, [MaybeNullWhen(false)] out T value) - where T : Type - { - if (type is T t) - { - value = t; - return true; - } - else if (type is AttributedType attributedType) - { - return IsType(cursor, attributedType.ModifiedType, out value); - } - else if (type is DecltypeType decltypeType) - { - return IsType(cursor, decltypeType.UnderlyingType, out value); - } - else if (type is DeducedType deducedType) - { - return IsType(cursor, deducedType.GetDeducedType, out value); - } - else if (type is DependentNameType dependentNameType) - { - if (dependentNameType.IsSugared) - { - return IsType(cursor, dependentNameType.Desugar, out value); - } - } - else if (type is ElaboratedType elaboratedType) - { - return IsType(cursor, elaboratedType.NamedType, out value); - } - else if (type is InjectedClassNameType injectedClassNameType) - { - return IsType(cursor, injectedClassNameType.InjectedTST, out value); - } - else if (type is PackExpansionType packExpansionType) - { - return IsType(cursor, packExpansionType.Pattern, out value); - } - else if (type is SubstTemplateTypeParmType substTemplateTypeParmType) - { - return IsType(cursor, substTemplateTypeParmType.ReplacementType, out value); - } - else if (type is TemplateSpecializationType templateSpecializationType) - { - if (templateSpecializationType.IsTypeAlias) - { - return IsType(cursor, templateSpecializationType.AliasedType, out value); - } - else if (templateSpecializationType.IsSugared) - { - return IsType(cursor, templateSpecializationType.Desugar, out value); - } - else if (templateSpecializationType.TemplateName.AsTemplateDecl is TemplateDecl templateDecl) - { - // We exclude InjectedClassNameType here to avoid infinite recursion. - if ((templateDecl.TemplatedDecl is TypeDecl typeDecl) && (typeDecl.TypeForDecl is not InjectedClassNameType )) - { - return IsType(cursor, typeDecl.TypeForDecl, out value); - } - } - } - else if (type is TemplateTypeParmType templateTypeParmType) - { - if (templateTypeParmType.IsSugared) - { - return IsType(cursor, templateTypeParmType.Decl.TypeForDecl, out value); - } - } - else if (type is TypedefType typedefType) - { - return IsType(cursor, typedefType.Decl.UnderlyingType, out value); - } - else if (type is UsingType usingType) - { - if (usingType.IsSugared) - { - return IsType(cursor, usingType.Desugar, out value); - } - } - - value = default; - return false; - } - - private static bool IsTypeConstantOrIncompleteArray(Expr expr) - => IsTypeConstantOrIncompleteArray(expr, out _); - - private static bool IsTypeConstantOrIncompleteArray(Expr expr, [MaybeNullWhen(false)] out ArrayType arrayType) - => IsTypeConstantOrIncompleteArray(expr, expr.Type, out arrayType); - - private bool IsTypeConstantOrIncompleteArray(ValueDecl valueDecl) - => IsTypeConstantOrIncompleteArray(valueDecl, out _); - - private static bool IsTypeConstantOrIncompleteArray(ValueDecl valueDecl, [MaybeNullWhen(false)] out ArrayType arrayType) - => IsTypeConstantOrIncompleteArray(valueDecl, valueDecl.Type, out arrayType); - - private static bool IsTypeConstantOrIncompleteArray(Cursor? cursor, Type type) - => IsTypeConstantOrIncompleteArray(cursor, type, out _); - - private static bool IsTypeConstantOrIncompleteArray(Cursor? cursor, Type type, [MaybeNullWhen(false)] out ArrayType arrayType) - => IsType(cursor, type, out arrayType) - && (arrayType is ConstantArrayType or IncompleteArrayType); - - private static bool IsTypePointerOrReference(Expr expr) - => IsTypePointerOrReference(expr, expr.Type); - - private static bool IsTypePointerOrReference(ValueDecl valueDecl) - => IsTypePointerOrReference(valueDecl, valueDecl.Type); - - private static bool IsTypePointerOrReference(Cursor? cursor, Type type) - => IsType(cursor, type) - || IsType(cursor, type); - - private static bool IsTypeVoid(Cursor? cursor, Type type) - => IsType(cursor, type, out var builtinType) - && (builtinType.Kind == CXType_Void); - - internal bool IsSupportedFixedSizedBufferType(string typeName) - { - switch (typeName) - { - case "bool": - case "byte": - case "char": - case "double": - case "float": - case "int": - case "long": - case "sbyte": - case "short": - case "ushort": - case "uint": - case "ulong": - { - // We want to prefer InlineArray in modern code, as it is safer and supports more features - return Config.GenerateCompatibleCode; - } - - default: - { - return false; - } - } - } - - private static bool IsTransparentStructBoolean(PInvokeGeneratorTransparentStructKind kind) - => kind is PInvokeGeneratorTransparentStructKind.Boolean; - - private static bool IsTransparentStructHandle(PInvokeGeneratorTransparentStructKind kind) - => kind is PInvokeGeneratorTransparentStructKind.Handle - or PInvokeGeneratorTransparentStructKind.HandleWin32; - - private static bool IsTransparentStructHexBased(PInvokeGeneratorTransparentStructKind kind) - => IsTransparentStructHandle(kind) - || (kind == PInvokeGeneratorTransparentStructKind.TypedefHex); - - private bool IsUnchecked(string targetTypeName, Stmt stmt) - { - if (IsPrevContextDecl(out var parentVarDecl, out _)) - { - var cursorName = GetCursorName(parentVarDecl); - - if (cursorName.StartsWith("ClangSharpMacro_", StringComparison.Ordinal) && _config.WithTransparentStructs.TryGetValue(targetTypeName, out var transparentStruct)) - { - targetTypeName = transparentStruct.Name; - } - } - - switch (stmt.StmtClass) - { - // case CX_StmtClass_BinaryConditionalOperator: - - case CX_StmtClass_ConditionalOperator: - { - var conditionalOperator = (ConditionalOperator)stmt; - return IsUnchecked(targetTypeName, conditionalOperator.LHS) - || IsUnchecked(targetTypeName, conditionalOperator.RHS) - || IsUnchecked(targetTypeName, conditionalOperator.Handle.Evaluate); - } - - // case CX_StmtClass_AddrLabelExpr: - // case CX_StmtClass_ArrayInitIndexExpr: - // case CX_StmtClass_ArrayInitLoopExpr: - - case CX_StmtClass_ArraySubscriptExpr: - { - var arraySubscriptExpr = (ArraySubscriptExpr)stmt; - return IsUnchecked(targetTypeName, arraySubscriptExpr.LHS) - || IsUnchecked(targetTypeName, arraySubscriptExpr.RHS); - } - - // case CX_StmtClass_ArrayTypeTraitExpr: - // case CX_StmtClass_AsTypeExpr: - // case CX_StmtClass_AtomicExpr: - - case CX_StmtClass_BinaryOperator: - { - var binaryOperator = (BinaryOperator)stmt; - return IsUnchecked(targetTypeName, binaryOperator.LHS) - || IsUnchecked(targetTypeName, binaryOperator.RHS) - || IsUnchecked(targetTypeName, binaryOperator.Handle.Evaluate) - || IsOverflow(binaryOperator); - } - - // case CX_StmtClass_CompoundAssignOperator: - // case CX_StmtClass_BlockExpr: - // case CX_StmtClass_CXXBindTemporaryExpr: - - case CX_StmtClass_CXXBoolLiteralExpr: - { - return false; - } - - case CX_StmtClass_CXXConstructExpr: - { - return false; - } - - case CX_StmtClass_CXXTemporaryObjectExpr: - { - return false; - } - - case CX_StmtClass_CXXDefaultArgExpr: - { - return false; - } - - case CX_StmtClass_CXXDefaultInitExpr: - { - return false; - } - - // case CX_StmtClass_CXXDeleteExpr: - - case CX_StmtClass_CXXDependentScopeMemberExpr: - { - return false; - } - - // case CX_StmtClass_CXXFoldExpr: - // case CX_StmtClass_CXXInheritedCtorInitExpr: - - case CX_StmtClass_CXXNewExpr: - { - return false; - } - - // case CX_StmtClass_CXXNoexceptExpr: - - case CX_StmtClass_CXXNullPtrLiteralExpr: - { - return false; - } - - // case CX_StmtClass_CXXPseudoDestructorExpr: - // case CX_StmtClass_CXXRewrittenBinaryOperator: - // case CX_StmtClass_CXXScalarValueInitExpr: - // case CX_StmtClass_CXXStdInitializerListExpr: - - case CX_StmtClass_CXXThisExpr: - { - return false; - } - - // case CX_StmtClass_CXXThrowExpr: - // case CX_StmtClass_CXXTypeidExpr: - // case CX_StmtClass_CXXUnresolvedConstructExpr: - - case CX_StmtClass_CXXUuidofExpr: - { - return false; - } - - case CX_StmtClass_CallExpr: - { - return false; - } - - // case CX_StmtClass_CUDAKernelCallExpr: - - case CX_StmtClass_CXXMemberCallExpr: - { - return false; - } - - case CX_StmtClass_CXXOperatorCallExpr: - { - return false; - } - - // case CX_StmtClass_UserDefinedLiteral: - // case CX_StmtClass_BuiltinBitCastExpr: - - case CX_StmtClass_CStyleCastExpr: - case CX_StmtClass_CXXStaticCastExpr: - case CX_StmtClass_CXXFunctionalCastExpr: - { - var explicitCastExpr = (ExplicitCastExpr)stmt; - var explicitCastExprTypeName = GetRemappedTypeName(explicitCastExpr, context: null, explicitCastExpr.Type, out _); - - return IsUnchecked(targetTypeName, explicitCastExpr.SubExprAsWritten) - || IsUnchecked(targetTypeName, explicitCastExpr.Handle.Evaluate) - || (IsUnsigned(targetTypeName) != IsUnsigned(explicitCastExprTypeName)); - } - - case CX_StmtClass_CXXConstCastExpr: - case CX_StmtClass_CXXDynamicCastExpr: - case CX_StmtClass_CXXReinterpretCastExpr: - { - var namedCastExpr = (CXXNamedCastExpr)stmt; - - return IsUnchecked(targetTypeName, namedCastExpr.SubExprAsWritten) - || IsUnchecked(targetTypeName, namedCastExpr.Handle.Evaluate); - } - - // case CX_StmtClass_ObjCBridgedCastExpr: - - case CX_StmtClass_ImplicitCastExpr: - { - var implicitCastExpr = (ImplicitCastExpr)stmt; - - return IsUnchecked(targetTypeName, implicitCastExpr.SubExprAsWritten) - || IsUnchecked(targetTypeName, implicitCastExpr.Handle.Evaluate); - } - - case CX_StmtClass_CharacterLiteral: - { - return false; - } - - // case CX_StmtClass_ChooseExpr: - // case CX_StmtClass_CompoundLiteralExpr: - // case CX_StmtClass_ConceptSpecializationExpr: - // case CX_StmtClass_ConvertVectorExpr: - // case CX_StmtClass_CoawaitExpr: - // case CX_StmtClass_CoyieldExpr: - - case CX_StmtClass_DeclRefExpr: - { - var declRefExpr = (DeclRefExpr)stmt; - return (declRefExpr.Decl is VarDecl varDecl) && varDecl.HasInit && IsUnchecked(targetTypeName, varDecl.Init); - } - - // case CX_StmtClass_DependentCoawaitExpr: - // case CX_StmtClass_DependentScopeDeclRefExpr: - // case CX_StmtClass_DesignatedInitExpr: - // case CX_StmtClass_DesignatedInitUpdateExpr: - // case CX_StmtClass_ExpressionTraitExpr: - // case CX_StmtClass_ExtVectorElementExpr: - // case CX_StmtClass_FixedPointLiteral: - - case CX_StmtClass_FloatingLiteral: - { - return false; - } - - // case CX_StmtClass_ConstantExpr: - - case CX_StmtClass_ExprWithCleanups: - { - var exprWithCleanups = (ExprWithCleanups)stmt; - return IsUnchecked(targetTypeName, exprWithCleanups.SubExpr); - } - - // case CX_StmtClass_FunctionParmPackExpr: - // case CX_StmtClass_GNUNullExpr: - // case CX_StmtClass_GenericSelectionExpr: - // case CX_StmtClass_ImaginaryLiteral: - // case CX_StmtClass_ImplicitValueInitExpr: - - case CX_StmtClass_InitListExpr: - { - return false; - } - - case CX_StmtClass_IntegerLiteral: - { - var integerLiteral = (IntegerLiteral)stmt; - var signedValue = integerLiteral.Value; - return IsUnchecked(targetTypeName, signedValue, integerLiteral.IsNegative, isHex: integerLiteral.ValueString.StartsWith("0x", StringComparison.Ordinal)); - } - - case CX_StmtClass_LambdaExpr: - { - return false; - } - - // case CX_StmtClass_MSPropertyRefExpr: - // case CX_StmtClass_MSPropertySubscriptExpr: - - case CX_StmtClass_MaterializeTemporaryExpr: - { - return false; - } - - case CX_StmtClass_MemberExpr: - { - return false; - } - - // case CX_StmtClass_NoInitExpr: - // case CX_StmtClass_ArraySectionExpr: - // case CX_StmtClass_ObjCArrayLiteral: - // case CX_StmtClass_ObjCAvailabilityCheckExpr: - // case CX_StmtClass_ObjCBoolLiteralExpr: - // case CX_StmtClass_ObjCBoxedExpr: - // case CX_StmtClass_ObjCDictionaryLiteral: - // case CX_StmtClass_ObjCEncodeExpr: - // case CX_StmtClass_ObjCIndirectCopyRestoreExpr: - // case CX_StmtClass_ObjCIsaExpr: - // case CX_StmtClass_ObjCIvarRefExpr: - // case CX_StmtClass_ObjCMessageExpr: - // case CX_StmtClass_ObjCPropertyRefExpr: - // case CX_StmtClass_ObjCProtocolExpr: - // case CX_StmtClass_ObjCSelectorExpr: - // case CX_StmtClass_ObjCStringLiteral: - // case CX_StmtClass_ObjCSubscriptRefExpr: - - case CX_StmtClass_OffsetOfExpr: - { - return false; - } - - // case CX_StmtClass_OpaqueValueExpr: - // case CX_StmtClass_UnresolvedLookupExpr: - // case CX_StmtClass_UnresolvedMemberExpr: - // case CX_StmtClass_PackExpansionExpr: - - case CX_StmtClass_ParenExpr: - { - var parenExpr = (ParenExpr)stmt; - return IsUnchecked(targetTypeName, parenExpr.SubExpr) - || IsUnchecked(targetTypeName, parenExpr.Handle.Evaluate); - } - - case CX_StmtClass_ParenListExpr: - { - var parenListExpr = (ParenListExpr)stmt; - - foreach (var expr in parenListExpr.Exprs) - { - if (IsUnchecked(targetTypeName, expr) || IsUnchecked(targetTypeName, expr.Handle.Evaluate)) - { - return true; - } - } - - return false; - } - - // case CX_StmtClass_PredefinedExpr: - // case CX_StmtClass_PseudoObjectExpr: - // case CX_StmtClass_RequiresExpr: - // case CX_StmtClass_ShuffleVectorExpr: - // case CX_StmtClass_SizeOfPackExpr: - // case CX_StmtClass_SourceLocExpr: - // case CX_StmtClass_StmtExpr: - - case CX_StmtClass_StringLiteral: - { - return false; - } - - case CX_StmtClass_SubstNonTypeTemplateParmExpr: - { - return false; - } - - // case CX_StmtClass_SubstNonTypeTemplateParmPackExpr: - // case CX_StmtClass_TypeTraitExpr: - // case CX_StmtClass_TypoExpr: - - case CX_StmtClass_UnaryExprOrTypeTraitExpr: - { - var unaryExprOrTypeTraitExpr = (UnaryExprOrTypeTraitExpr)stmt; - - var argumentType = unaryExprOrTypeTraitExpr.TypeOfArgument; - - long alignment32 = -1; - long alignment64 = -1; - - GetTypeSize(unaryExprOrTypeTraitExpr, argumentType, ref alignment32, ref alignment64, out var size32, out var size64); - - switch (unaryExprOrTypeTraitExpr.Kind) - { - case CX_UETT_SizeOf: - { - switch (targetTypeName) - { - case "bool": - case "Boolean": - case "byte": - case "Byte": - case "char": - case "Char": - case "ushort": - case "UInt16": - case "uint": - case "UInt32": - case "nuint": - case "sbyte": - case "SByte": - case "short": - case "Int16": - { - return (size32 != size64) || !IsPrevContextDecl(out _, out _); - } - - case "ulong": - case "UInt64": - case "int": - case "Int32": - case "nint": - case "long": - case "Int64": - { - return false; - } - - default: - { - return false; - } - } - } - - default: - { - return false; - } - } - } - - case CX_StmtClass_UnaryOperator: - { - var unaryOperator = (UnaryOperator)stmt; - - if (IsUnchecked(targetTypeName, unaryOperator.SubExpr)) - { - return true; - } - - var evaluation = unaryOperator.Handle.Evaluate; - - if (IsUnchecked(targetTypeName, evaluation)) - { - return true; - } - - var sourceTypeName = GetTypeName(stmt, context: null, type: unaryOperator.SubExpr.Type, ignoreTransparentStructsWhereRequired: false, isTemplate: false, nativeTypeName: out _); - - switch (unaryOperator.Opcode) - { - case CXUnaryOperator_Minus: - { - return IsUnsigned(targetTypeName); - } - - case CXUnaryOperator_Not: - { - return IsUnsigned(targetTypeName) != IsUnsigned(sourceTypeName); - } - - default: - { - return false; - } - } - } - - // case CX_StmtClass_VAArgExpr: - - default: - { - AddDiagnostic(DiagnosticLevel.Warning, $"Unsupported statement class: '{stmt.StmtClassName}'. Generated bindings may not be unchecked.", stmt); - return false; - } - } - - bool IsOverflow(BinaryOperator binaryOperator) - { - var lhs = binaryOperator.LHS; - var rhs = binaryOperator.RHS; - - long lhsValue, rhsValue; - - if (IsStmtAsWritten(lhs, out var lhsIntegerLiteral, removeParens: true)) - { - lhsValue = lhsIntegerLiteral.Value; - } - else - { - var lhsEvaluation = lhs.Handle.Evaluate; - - if (lhsEvaluation.Kind == CXEval_Int) - { - lhsValue = lhsEvaluation.AsInt; - } - else - { - return false; - } - } - - if (IsStmtAsWritten(rhs, out var rhsIntegerLiteral, removeParens: true)) - { - rhsValue = rhsIntegerLiteral.Value; - } - else - { - var rhsEvaluation = rhs.Handle.Evaluate; - - if (rhsEvaluation.Kind == CXEval_Int) - { - rhsValue = rhsEvaluation.AsInt; - } - else - { - return false; - } - } - - var targetTypeName = GetRemappedTypeName(binaryOperator, context: null, binaryOperator.Type, out _, skipUsing: true); - var isUnsigned = IsUnsigned(targetTypeName); - - switch (binaryOperator.Opcode) - { - case CXBinaryOperator_Add: - { - return isUnsigned - ? (ulong)lhsValue + (ulong)rhsValue < (ulong)lhsValue - : lhsValue + rhsValue < lhsValue; - } - - case CXBinaryOperator_Sub: - { - return isUnsigned - ? (ulong)lhsValue - (ulong)rhsValue > (ulong)lhsValue - : lhsValue - rhsValue > lhsValue; - } - - default: - { - return false; - } - } - } - } - - private static bool IsUnchecked(string typeName, CXEvalResult evalResult) - { - if (evalResult.Kind != CXEval_Int) - { - return false; - } - - var signedValue = evalResult.AsLongLong; - return IsUnchecked(typeName, signedValue, signedValue < 0, isHex: false); - } - - private static bool IsUnchecked(string typeName, long signedValue, bool isNegative, bool isHex) - { - switch (typeName) - { - case "byte": - case "Byte": - { - var unsignedValue = unchecked((ulong)signedValue); - return unsignedValue is < byte.MinValue or > byte.MaxValue; - } - - case "char": - case "Char": - { - var unsignedValue = unchecked((ulong)signedValue); - return unsignedValue is < char.MinValue or > char.MaxValue; - } - - case "ushort": - case "UInt16": - { - var unsignedValue = unchecked((ulong)signedValue); - return unsignedValue is < ushort.MinValue or > ushort.MaxValue; - } - - case "uint": - case "UInt32": - case "nuint": - case "UIntPtr": - { - return false; - } - - case "ulong": - case "UInt64": - { - return false; - } - - case "sbyte": - case "SByte": - { - return (signedValue < sbyte.MinValue) || (sbyte.MaxValue < signedValue) || (isNegative && isHex); - } - - case "short": - case "Int16": - { - return (signedValue < short.MinValue) || (short.MaxValue < signedValue) || (isNegative && isHex); - } - - case "int": - case "Int32": - case "nint": - case "IntPtr": - { - return (signedValue < int.MinValue) || (int.MaxValue < signedValue) || (isNegative && isHex); - } - - case "long": - case "Int64": - { - return (signedValue < long.MinValue) || (long.MaxValue < signedValue) || (isNegative && isHex); - } - - default: - { - return false; - } - } - } - - private bool IsUnsafe(FieldDecl fieldDecl) - { - var type = fieldDecl.Type; - - if (IsType(fieldDecl, out _) && IsTypeConstantOrIncompleteArray(fieldDecl, type)) - { - var remappedName = GetRemappedTypeName(fieldDecl, context: null, type, out _, skipUsing: true, ignoreTransparentStructsWhereRequired: false); - return IsSupportedFixedSizedBufferType(remappedName); - } - - return IsUnsafe(fieldDecl, type); - } - - private bool IsUnsafe(FunctionDecl functionDecl) - { - var name = GetRemappedCursorName(functionDecl); - - if (_config.WithManualImports.Contains(name)) - { - return true; - } - - if (IsUnsafe(functionDecl, functionDecl.ReturnType)) - { - return true; - } - - foreach (var parmVarDecl in functionDecl.Parameters) - { - if (IsUnsafe(parmVarDecl)) - { - return true; - } - } - - return false; - } - - private bool IsUnsafe(ParmVarDecl parmVarDecl) - { - var type = parmVarDecl.Type; - return IsUnsafe(parmVarDecl, type); - } - - private bool IsUnsafe(RecordDecl recordDecl) - { - foreach (var decl in recordDecl.Decls) - { - if ((decl is FieldDecl fieldDecl) && IsUnsafe(fieldDecl)) - { - return true; - } - else if ((decl is RecordDecl nestedRecordDecl) && nestedRecordDecl.IsAnonymousStructOrUnion && (IsUnsafe(nestedRecordDecl) || Config.GenerateCompatibleCode)) - { - return true; - } - } - return (recordDecl is CXXRecordDecl cxxRecordDecl) && (HasVtbl(cxxRecordDecl, out var hasBaseVtbl) || hasBaseVtbl || HasUnsafeMethod(cxxRecordDecl)); - } - - private bool IsUnsafe(TypedefDecl typedefDecl, FunctionProtoType functionProtoType) - { - var returnType = functionProtoType.ReturnType; - - if (IsUnsafe(typedefDecl, returnType)) - { - return true; - } - - foreach (var paramType in functionProtoType.ParamTypes) - { - if (IsUnsafe(typedefDecl, paramType)) - { - return true; - } - } - - return false; - } - - private bool IsUnsafe(NamedDecl namedDecl, Type type) - { - var remappedName = GetRemappedTypeName(namedDecl, context: null, type, out _, skipUsing: true, ignoreTransparentStructsWhereRequired: false); - return remappedName.Contains('*', StringComparison.Ordinal); - } - - private static bool IsUnsigned(string typeName) - { - switch (typeName) - { - case "byte": - case "Byte": - case "char": - case "Char": - case "nuint": - case "UInt16": - case "uint": - case "UInt32": - case "ulong": - case "UInt64": - case "UIntPtr": - case "ushort": - case var _ when typeName.EndsWith('*'): - { - return true; - } - - case "Int16": - case "int": - case "Int32": - case "long": - case "Int64": - case "nint": - case "sbyte": - case "SByte": - case "short": - { - return false; - } - - default: - { - return false; - } - } + return hasVtbl; } private bool NeedsReturnFixup(CXXMethodDecl cxxMethodDecl) diff --git a/sources/ClangSharpPInvokeGenerator/Program.Options.cs b/sources/ClangSharpPInvokeGenerator/Program.Options.cs new file mode 100644 index 00000000..69073b09 --- /dev/null +++ b/sources/ClangSharpPInvokeGenerator/Program.Options.cs @@ -0,0 +1,625 @@ +// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +using System; +using System.CommandLine; +using System.CommandLine.Help; +using System.CommandLine.Invocation; + +namespace ClangSharp; + +internal static partial class Program +{ + private static readonly string[] s_additionalOptionAliases = ["--additional", "-a"]; + private static readonly string[] s_configOptionAliases = ["--config", "-c"]; + private static readonly string[] s_defineMacroOptionAliases = ["--define-macro", "-D"]; + private static readonly string[] s_excludeOptionAliases = ["--exclude", "-e"]; + private static readonly string[] s_fileOptionAliases = ["--file", "-f"]; + private static readonly string[] s_fileDirectionOptionAliases = ["--file-directory", "-F"]; + private static readonly string[] s_headerOptionAliases = ["--headerFile", "-hf"]; + private static readonly string[] s_includeOptionAliases = ["--include", "-i"]; + private static readonly string[] s_includeDirectoryOptionAliases = ["--include-directory", "-I"]; + private static readonly string[] s_languageOptionAliases = ["--language", "-x"]; + private static readonly string[] s_libraryOptionAliases = ["--libraryPath", "-l"]; + private static readonly string[] s_methodClassNameOptionAliases = ["--methodClassName", "-m"]; + private static readonly string[] s_namespaceOptionAliases = ["--namespace", "-n"]; + private static readonly string[] s_nativeTypeNamesStripOptionAliases = ["--nativeTypeNamesToStrip"]; + private static readonly string[] s_outputModeOptionAliases = ["--output-mode", "-om"]; + private static readonly string[] s_outputOptionAliases = ["--output", "-o"]; + private static readonly string[] s_prefixStripOptionAliases = ["--prefixStrip", "-p"]; + private static readonly string[] s_remapOptionAliases = ["--remap", "-r"]; + private static readonly string[] s_stdOptionAliases = ["--std", "-std"]; + private static readonly string[] s_testOutputOptionAliases = ["--test-output", "-to"]; + private static readonly string[] s_traverseOptionAliases = ["--traverse", "-t"]; + private static readonly string[] s_versionOptionAliases = ["--version", "-v"]; + private static readonly string[] s_withAccessSpecifierOptionAliases = ["--with-access-specifier", "-was"]; + private static readonly string[] s_withAttributeOptionAliases = ["--with-attribute", "-wa"]; + private static readonly string[] s_withCallConvOptionAliases = ["--with-callconv", "-wcc"]; + private static readonly string[] s_withClassOptionAliases = ["--with-class", "-wc"]; + private static readonly string[] s_withGuidOptionAliases = ["--with-guid", "-wg"]; + private static readonly string[] s_withLengthOptionAliases = ["--with-length", "-wl"]; + private static readonly string[] s_withLibraryPathOptionAliases = ["--with-librarypath", "-wlb"]; + private static readonly string[] s_withManualImportOptionAliases = ["--with-manual-import", "-wmi"]; + private static readonly string[] s_withNamespaceOptionAliases = ["--with-namespace", "-wn"]; + private static readonly string[] s_withPackingOptionAliases = ["--with-packing", "-wp"]; + private static readonly string[] s_withReadonlyOptionAliases = ["--with-readonly", "-wro"]; + private static readonly string[] s_withSetLastErrorOptionAliases = ["--with-setlasterror", "-wsle"]; + private static readonly string[] s_withSuppressGCTransitionOptionAliases = ["--with-suppressgctransition", "-wsgct"]; + private static readonly string[] s_withTransparentStructOptionAliases = ["--with-transparent-struct", "-wts"]; + private static readonly string[] s_withTypeOptionAliases = ["--with-type", "-wt"]; + private static readonly string[] s_withUsingOptionAliases = ["--with-using", "-wu"]; + + private static readonly Option s_additionalOption = GetAdditionalOption(); + private static readonly Option s_configOption = GetConfigOption(); + private static readonly Option s_defineMacros = GetDefineMacroOption(); + private static readonly Option s_excludedNames = GetExcludeOption(); + private static readonly Option s_files = GetFileOption(); + private static readonly Option s_fileDirectory = GetFileDirectoryOption(); + private static readonly Option s_headerFile = GetHeaderOption(); + private static readonly Option s_includedNames = GetIncludeOption(); + private static readonly Option s_includeDirectories = GetIncludeDirectoryOption(); + private static readonly Option s_language = GetLanguageOption(); + private static readonly Option s_libraryPath = GetLibraryOption(); + private static readonly Option s_methodClassName = GetMethodClassNameOption(); + private static readonly Option s_methodPrefixToStrip = GetPrefixStripOption(); + private static readonly Option s_namespaceName = GetNamespaceOption(); + private static readonly Option s_nativeTypeNamesToStrip = GetNativeTypeNamesStripOption(); + private static readonly Option s_outputLocation = GetOutputOption(); + private static readonly Option s_outputMode = GetOutputModeOption(); + private static readonly Option s_remappedNameValuePairs = GetRemapOption(); + private static readonly Option s_std = GetStdOption(); + private static readonly Option s_testOutputLocation = GetTestOutputOption(); + private static readonly Option s_traversalNames = GetTraverseOption(); + private static readonly Option s_versionOption = GetVersionOption(); + private static readonly Option s_withAccessSpecifierNameValuePairs = GetWithAccessSpecifierOption(); + private static readonly Option s_withAttributeNameValuePairs = GetWithAttributeOption(); + private static readonly Option s_withCallConvNameValuePairs = GetWithCallConvOption(); + private static readonly Option s_withClassNameValuePairs = GetWithClassOption(); + private static readonly Option s_withGuidNameValuePairs = GetWithGuidOption(); + private static readonly Option s_withLengthNameValuePairs = GetWithLengthOption(); + private static readonly Option s_withLibraryPathNameValuePairs = GetWithLibraryPathOption(); + private static readonly Option s_withManualImports = GetWithManualImportOption(); + private static readonly Option s_withNamespaceNameValuePairs = GetWithNamespaceOption(); + private static readonly Option s_withPackingNameValuePairs = GetWithPackingOption(); + private static readonly Option s_withReadonlys = GetWithReadonlyOption(); + private static readonly Option s_withSetLastErrors = GetWithSetLastErrorOption(); + private static readonly Option s_withSuppressGCTransitions = GetWithSuppressGCTransitionOption(); + private static readonly Option s_withTransparentStructNameValuePairs = GetWithTransparentStructOption(); + private static readonly Option s_withTypeNameValuePairs = GetWithTypeOption(); + private static readonly Option s_withUsingNameValuePairs = GetWithUsingOption(); + + private static readonly RootCommand s_rootCommand = GetRootCommand(); + + private static readonly TwoColumnHelpRow[] s_configOptions = + [ + new TwoColumnHelpRow("?, h, help", "Show help and usage information for -c, --config"), + + new TwoColumnHelpRow("", ""), + new TwoColumnHelpRow("# Codegen Options", ""), + new TwoColumnHelpRow("", ""), + + new TwoColumnHelpRow("compatible-codegen", "Bindings should be generated with .NET Standard 2.0 compatibility. Setting this disables preview code generation."), + new TwoColumnHelpRow("default-codegen", "Bindings should be generated for the current LTS version of .NET/C#. This is currently .NET 8/C# 12."), + new TwoColumnHelpRow("latest-codegen", "Bindings should be generated for the current STS version of .NET/C#. This is currently .NET 10/C# 14."), + new TwoColumnHelpRow("preview-codegen", "Bindings should be generated for the preview version of .NET/C#. This is currently .NET 10/C# 14."), + + new TwoColumnHelpRow("", ""), + new TwoColumnHelpRow("# File Options", ""), + new TwoColumnHelpRow("", ""), + + new TwoColumnHelpRow("single-file", "Bindings should be generated to a single output file. This is the default."), + new TwoColumnHelpRow("multi-file", "Bindings should be generated so there is approximately one type per file."), + + new TwoColumnHelpRow("", ""), + new TwoColumnHelpRow("# Type Options", ""), + new TwoColumnHelpRow("", ""), + + new TwoColumnHelpRow("unix-types", "Bindings should be generated assuming Unix defaults. This is the default on Unix platforms."), + new TwoColumnHelpRow("windows-types", "Bindings should be generated assuming Windows defaults. This is the default on Windows platforms."), + + new TwoColumnHelpRow("", ""), + new TwoColumnHelpRow("# Exclusion Options", ""), + new TwoColumnHelpRow("", ""), + + new TwoColumnHelpRow("exclude-anonymous-field-helpers", "The helper ref properties generated for fields in nested anonymous structs and unions should not be generated."), + new TwoColumnHelpRow("exclude-com-proxies", "Types recognized as COM proxies should not have bindings generated. These are currently function declarations ending with _UserFree, _UserMarshal, _UserSize, _UserUnmarshal, _Proxy, or _Stub."), + new TwoColumnHelpRow("exclude-default-remappings", "Default remappings for well known types should not be added. This currently includes intptr_t, ptrdiff_t, size_t, and uintptr_t"), + new TwoColumnHelpRow("exclude-empty-records", "Bindings for records that contain no members should not be generated. These are commonly encountered for opaque handle like types such as HWND."), + new TwoColumnHelpRow("exclude-enum-operators", "Bindings for operators over enum types should not be generated. These are largely unnecessary in C# as the operators are available by default."), + new TwoColumnHelpRow("exclude-fnptr-codegen", "Generated bindings for latest or preview codegen should not use function pointers."), + new TwoColumnHelpRow("exclude-funcs-with-body", "Bindings for functions with bodies should not be generated."), + new TwoColumnHelpRow("exclude-using-statics-for-enums", "Enum usages should be fully qualified and should not include a corresponding 'using static EnumName;'"), + + new TwoColumnHelpRow("", ""), + new TwoColumnHelpRow("# Vtbl Options", ""), + new TwoColumnHelpRow("", ""), + + new TwoColumnHelpRow("explicit-vtbls", "VTBLs should have an explicit type generated with named fields per entry."), + new TwoColumnHelpRow("implicit-vtbls", "VTBLs should be implicit to reduce metadata bloat. This is the current default"), + new TwoColumnHelpRow("trimmable-vtbls", "VTBLs should be defined but not used in helper methods to reduce metadata bloat when trimming."), + + new TwoColumnHelpRow("", ""), + new TwoColumnHelpRow("# Test Options", ""), + new TwoColumnHelpRow("", ""), + + new TwoColumnHelpRow("generate-tests-nunit", "Basic tests validating size, blittability, and associated metadata should be generated for NUnit."), + new TwoColumnHelpRow("generate-tests-xunit", "Basic tests validating size, blittability, and associated metadata should be generated for XUnit."), + + new TwoColumnHelpRow("", ""), + new TwoColumnHelpRow("# Generation Options", ""), + new TwoColumnHelpRow("", ""), + + new TwoColumnHelpRow("generate-aggressive-inlining", "[MethodImpl(MethodImplOptions.AggressiveInlining)] should be added to generated helper functions."), + new TwoColumnHelpRow("generate-callconv-member-function", "Instance function pointers should use [CallConvMemberFunction] where applicable."), + new TwoColumnHelpRow("generate-cpp-attributes", "[CppAttributeList(\"\")] should be generated to document the encountered C++ attributes."), + new TwoColumnHelpRow("generate-disable-runtime-marshalling", "[assembly: DisableRuntimeMarshalling] should be generated."), + new TwoColumnHelpRow("generate-doc-includes", " xml documentation tags should be generated for declarations."), + new TwoColumnHelpRow("generate-file-scoped-namespaces", "Namespaces should be scoped to the file to reduce nesting."), + new TwoColumnHelpRow("generate-guid-member", "Types with an associated GUID should have a corresponding member generated."), + new TwoColumnHelpRow("generate-helper-types", "Code files should be generated for various helper attributes and declared transparent structs."), + new TwoColumnHelpRow("generate-macro-bindings", "Bindings for macro-definitions should be generated. This currently only works with value like macros and not function-like ones."), + new TwoColumnHelpRow("generate-marker-interfaces", "Bindings for marker interfaces representing native inheritance hierarchies should be generated."), + new TwoColumnHelpRow("generate-native-bitfield-attribute", "[NativeBitfield(\"\", offset: #, length: #)] attribute should be generated to document the encountered bitfield layout."), + new TwoColumnHelpRow("generate-native-inheritance-attribute", "[NativeInheritance(\"\")] attribute should be generated to document the encountered C++ base type."), + new TwoColumnHelpRow("generate-generic-pointer-wrapper", "Pointer should be used for limited generic type support."), + new TwoColumnHelpRow("generate-setslastsystemerror-attribute", "[SetsLastSystemError] attribute should be generated rather than using SetLastError = true."), + new TwoColumnHelpRow("generate-template-bindings", "Bindings for template-definitions should be generated. This is currently experimental."), + new TwoColumnHelpRow("generate-unmanaged-constants", "Unmanaged constants should be generated using static ref readonly properties. This is currently experimental."), + new TwoColumnHelpRow("generate-vtbl-index-attribute", "[VtblIndex(#)] attribute should be generated to document the underlying VTBL index for a helper method."), + + new TwoColumnHelpRow("", ""), + new TwoColumnHelpRow("# Stripping Options", ""), + new TwoColumnHelpRow("", ""), + + new TwoColumnHelpRow("strip-enum-member-type-name", "Strips the enum type name from the beginning of its member names."), + + new TwoColumnHelpRow("", ""), + new TwoColumnHelpRow("# Logging Options", ""), + new TwoColumnHelpRow("", ""), + + new TwoColumnHelpRow("log-exclusions", "A list of excluded declaration types should be generated. This will also log if the exclusion was due to an exact or partial match."), + new TwoColumnHelpRow("log-potential-typedef-remappings", "A list of potential typedef remappings should be generated. This can help identify missing remappings."), + new TwoColumnHelpRow("log-visited-files", "A list of the visited files should be generated. This can help identify traversal issues."), + ]; + + private static Option GetAdditionalOption() + { + return new Option( + aliases: s_additionalOptionAliases, + description: "An argument to pass to Clang when parsing the input files.", + getDefaultValue: Array.Empty + ) { + AllowMultipleArgumentsPerToken = true + }; + } + + private static Option GetConfigOption() + { + return new Option( + aliases: s_configOptionAliases, + description: "A configuration option that controls how the bindings are generated. Specify 'help' to see the available options.", + getDefaultValue: Array.Empty + ) { + AllowMultipleArgumentsPerToken = true + }; + } + + private static Option GetDefineMacroOption() + { + return new Option( + aliases: s_defineMacroOptionAliases, + description: "Define to (or 1 if omitted).", + getDefaultValue: Array.Empty + ) { + AllowMultipleArgumentsPerToken = true + }; + } + + private static Option GetExcludeOption() + { + return new Option( + aliases: s_excludeOptionAliases, + description: "A declaration name to exclude from binding generation.", + getDefaultValue: Array.Empty + ) { + AllowMultipleArgumentsPerToken = true + }; + } + + private static Option GetFileOption() + { + return new Option( + aliases: s_fileOptionAliases, + description: "A file to parse and generate bindings for.", + getDefaultValue: Array.Empty + ) { + AllowMultipleArgumentsPerToken = true + }; + } + + private static Option GetFileDirectoryOption() + { + return new Option( + aliases: s_fileDirectionOptionAliases, + description: "The base path for files to parse.", + getDefaultValue: () => string.Empty + ); + } + + private static Option GetHeaderOption() + { + return new Option( + aliases: s_headerOptionAliases, + description: "A file which contains the header to prefix every generated file with.", + getDefaultValue: () => string.Empty + ); + } + + private static Option GetIncludeOption() + { + return new Option( + aliases: s_includeOptionAliases, + description: "A declaration name to include in binding generation.", + getDefaultValue: Array.Empty + ) { + AllowMultipleArgumentsPerToken = true + }; + } + + private static Option GetIncludeDirectoryOption() + { + return new Option( + aliases: s_includeDirectoryOptionAliases, + description: "Add directory to include search path.", + getDefaultValue: Array.Empty + ) { + AllowMultipleArgumentsPerToken = true + }; + } + + private static Option GetLanguageOption() + { + return new Option( + aliases: s_languageOptionAliases, + description: "Treat subsequent input files as having type .", + getDefaultValue: () => "c++" + ).FromAmong("c", "c++"); + } + + private static Option GetLibraryOption() + { + return new Option( + aliases: s_libraryOptionAliases, + description: "The string to use in the DllImport attribute used when generating bindings.", + getDefaultValue: () => string.Empty + ); + } + + private static Option GetMethodClassNameOption() + { + return new Option( + aliases: s_methodClassNameOptionAliases, + description: "The name of the static class that will contain the generated method bindings.", + getDefaultValue: () => "Methods" + ); + } + + private static Option GetNamespaceOption() + { + return new Option( + aliases: s_namespaceOptionAliases, + description: "The namespace in which to place the generated bindings.", + getDefaultValue: () => string.Empty + ); + } + + private static Option GetNativeTypeNamesStripOption() + { + return new Option( + aliases: s_nativeTypeNamesStripOptionAliases, + description: "The contents to strip from the generated NativeTypeName attributes.", + getDefaultValue: Array.Empty + ) { + AllowMultipleArgumentsPerToken = true + }; + } + + private static Option GetOutputModeOption() + { + return new Option( + aliases: s_outputModeOptionAliases, + description: "The mode describing how the information collected from the headers are presented in the resultant bindings.", + getDefaultValue: () => PInvokeGeneratorOutputMode.CSharp + ); + } + + private static Option GetOutputOption() + { + return new Option( + aliases: s_outputOptionAliases, + description: "The output location to write the generated bindings to.", + getDefaultValue: () => string.Empty + ); + } + + private static Option GetPrefixStripOption() + { + return new Option( + aliases: s_prefixStripOptionAliases, + description: "The prefix to strip from the generated method bindings.", + getDefaultValue: () => string.Empty + ); + } + + private static Option GetRemapOption() + { + return new Option( + aliases: s_remapOptionAliases, + description: "A declaration name to be remapped to another name during binding generation.", + getDefaultValue: Array.Empty + ) { + AllowMultipleArgumentsPerToken = true + }; + } + + private static RootCommand GetRootCommand() + { + var rootCommand = new RootCommand("ClangSharp P/Invoke Binding Generator") + { + s_additionalOption, + s_configOption, + s_defineMacros, + s_excludedNames, + s_files, + s_fileDirectory, + s_headerFile, + s_includedNames, + s_includeDirectories, + s_language, + s_libraryPath, + s_methodClassName, + s_namespaceName, + s_outputMode, + s_outputLocation, + s_methodPrefixToStrip, + s_nativeTypeNamesToStrip, + s_remappedNameValuePairs, + s_std, + s_testOutputLocation, + s_traversalNames, + s_versionOption, + s_withAccessSpecifierNameValuePairs, + s_withAttributeNameValuePairs, + s_withCallConvNameValuePairs, + s_withClassNameValuePairs, + s_withGuidNameValuePairs, + s_withLengthNameValuePairs, + s_withLibraryPathNameValuePairs, + s_withManualImports, + s_withNamespaceNameValuePairs, + s_withPackingNameValuePairs, + s_withReadonlys, + s_withSetLastErrors, + s_withSuppressGCTransitions, + s_withTransparentStructNameValuePairs, + s_withTypeNameValuePairs, + s_withUsingNameValuePairs + }; + Handler.SetHandler(rootCommand, (Action)Run); + return rootCommand; + } + + private static Option GetStdOption() + { + return new Option( + aliases: s_stdOptionAliases, + description: "Language standard to compile for.", + getDefaultValue: () => "" + ); + } + + private static Option GetTestOutputOption() + { + return new Option( + aliases: s_testOutputOptionAliases, + description: "The output location to write the generated tests to.", + getDefaultValue: () => string.Empty + ); + } + + private static Option GetVersionOption() + { + return new Option( + aliases: s_versionOptionAliases, + description: "Prints the current version information for the tool and its native dependencies." + ) { + Arity = ArgumentArity.Zero + }; + } + + private static Option GetTraverseOption() + { + return new Option( + aliases: s_traverseOptionAliases, + description: "A file name included either directly or indirectly by -f that should be traversed during binding generation.", + getDefaultValue: Array.Empty + ) { + AllowMultipleArgumentsPerToken = true + }; + } + + private static Option GetWithAccessSpecifierOption() + { + return new Option( + aliases: s_withAccessSpecifierOptionAliases, + description: "An access specifier to be used with the given qualified or remapped declaration name during binding generation. Supports wildcards.", + getDefaultValue: Array.Empty + ) { + AllowMultipleArgumentsPerToken = true + }; + } + + private static Option GetWithAttributeOption() + { + return new Option( + aliases: s_withAttributeOptionAliases, + description: "An attribute to be added to the given remapped declaration name during binding generation. Supports wildcards.", + getDefaultValue: Array.Empty + ) { + AllowMultipleArgumentsPerToken = true + }; + } + + private static Option GetWithCallConvOption() + { + return new Option( + aliases: s_withCallConvOptionAliases, + description: "A calling convention to be used for the given declaration during binding generation. Supports wildcards.", + getDefaultValue: Array.Empty + ) { + AllowMultipleArgumentsPerToken = true + }; + } + + private static Option GetWithClassOption() + { + return new Option( + aliases: s_withClassOptionAliases, + description: "A class to be used for the given remapped constant or function declaration name during binding generation. Supports wildcards.", + getDefaultValue: Array.Empty + ) { + AllowMultipleArgumentsPerToken = true + }; + } + + private static Option GetWithGuidOption() + { + return new Option( + aliases: s_withGuidOptionAliases, + description: "A GUID to be used for the given declaration during binding generation. Supports wildcards.", + getDefaultValue: Array.Empty + ) { + AllowMultipleArgumentsPerToken = true + }; + } + + private static Option GetWithLengthOption() + { + return new Option( + aliases: s_withLengthOptionAliases, + description: "A length to be used for the given declaration during binding generation. Supports wildcards.", + getDefaultValue: Array.Empty + ) { + AllowMultipleArgumentsPerToken = true + }; + } + + private static Option GetWithLibraryPathOption() + { + return new Option( + aliases: s_withLibraryPathOptionAliases, + description: "A library path to be used for the given declaration during binding generation. Supports wildcards.", + getDefaultValue: Array.Empty + ) { + AllowMultipleArgumentsPerToken = true + }; + } + + private static Option GetWithManualImportOption() + { + return new Option( + aliases: s_withManualImportOptionAliases, + description: "A remapped function name to be treated as a manual import during binding generation. Supports wildcards.", + getDefaultValue: Array.Empty + ) { + AllowMultipleArgumentsPerToken = true + }; + } + + private static Option GetWithNamespaceOption() + { + return new Option( + aliases: s_withNamespaceOptionAliases, + description: "A namespace to be used for the given remapped declaration name during binding generation. Supports wildcards.", + getDefaultValue: Array.Empty + ) { + AllowMultipleArgumentsPerToken = true + }; + } + + private static Option GetWithReadonlyOption() + { + return new Option( + aliases: s_withReadonlyOptionAliases, + description: "Add the readonly modifier to a given instance method. Supports wildcards.", + getDefaultValue: Array.Empty + ) { + AllowMultipleArgumentsPerToken = true + }; + } + + private static Option GetWithSetLastErrorOption() + { + return new Option( + aliases: s_withSetLastErrorOptionAliases, + description: "Add the SetLastError=true modifier or SetsSystemLastError attribute to a given DllImport or UnmanagedFunctionPointer. Supports wildcards.", + getDefaultValue: Array.Empty + ) { + AllowMultipleArgumentsPerToken = true + }; + } + + private static Option GetWithSuppressGCTransitionOption() + { + return new Option( + aliases: s_withSuppressGCTransitionOptionAliases, + description: "Add the SuppressGCTransition calling convention to a given DllImport or UnmanagedFunctionPointer. Supports wildcards.", + getDefaultValue: Array.Empty + ) { + AllowMultipleArgumentsPerToken = true + }; + } + + private static Option GetWithTransparentStructOption() + { + return new Option( + aliases: s_withTransparentStructOptionAliases, + description: "A remapped type name to be treated as a transparent wrapper during binding generation. Supports wildcards.", + getDefaultValue: Array.Empty + ) { + AllowMultipleArgumentsPerToken = true + }; + } + + private static Option GetWithTypeOption() + { + return new Option( + aliases: s_withTypeOptionAliases, + description: "A type to be used for the given enum declaration during binding generation. Supports wildcards.", + getDefaultValue: Array.Empty + ) { + AllowMultipleArgumentsPerToken = true + }; + } + + private static Option GetWithUsingOption() + { + return new Option( + aliases: s_withUsingOptionAliases, + description: "A using directive to be included for the given remapped declaration name during binding generation. Supports wildcards.", + getDefaultValue: Array.Empty + ) { + AllowMultipleArgumentsPerToken = true + }; + } + + private static Option GetWithPackingOption() + { + return new Option( + aliases: s_withPackingOptionAliases, + description: "Overrides the StructLayoutAttribute.Pack property for the given type. Supports wildcards.", + getDefaultValue: Array.Empty + ) { + AllowMultipleArgumentsPerToken = true + }; + } +} diff --git a/sources/ClangSharpPInvokeGenerator/Program.cs b/sources/ClangSharpPInvokeGenerator/Program.cs index 5360233f..7e644a62 100644 --- a/sources/ClangSharpPInvokeGenerator/Program.cs +++ b/sources/ClangSharpPInvokeGenerator/Program.cs @@ -20,180 +20,8 @@ namespace ClangSharp; -internal static class Program +internal static partial class Program { - private static readonly string[] s_additionalOptionAliases = ["--additional", "-a"]; - private static readonly string[] s_configOptionAliases = ["--config", "-c"]; - private static readonly string[] s_defineMacroOptionAliases = ["--define-macro", "-D"]; - private static readonly string[] s_excludeOptionAliases = ["--exclude", "-e"]; - private static readonly string[] s_fileOptionAliases = ["--file", "-f"]; - private static readonly string[] s_fileDirectionOptionAliases = ["--file-directory", "-F"]; - private static readonly string[] s_headerOptionAliases = ["--headerFile", "-hf"]; - private static readonly string[] s_includeOptionAliases = ["--include", "-i"]; - private static readonly string[] s_includeDirectoryOptionAliases = ["--include-directory", "-I"]; - private static readonly string[] s_languageOptionAliases = ["--language", "-x"]; - private static readonly string[] s_libraryOptionAliases = ["--libraryPath", "-l"]; - private static readonly string[] s_methodClassNameOptionAliases = ["--methodClassName", "-m"]; - private static readonly string[] s_namespaceOptionAliases = ["--namespace", "-n"]; - private static readonly string[] s_nativeTypeNamesStripOptionAliases = ["--nativeTypeNamesToStrip"]; - private static readonly string[] s_outputModeOptionAliases = ["--output-mode", "-om"]; - private static readonly string[] s_outputOptionAliases = ["--output", "-o"]; - private static readonly string[] s_prefixStripOptionAliases = ["--prefixStrip", "-p"]; - private static readonly string[] s_remapOptionAliases = ["--remap", "-r"]; - private static readonly string[] s_stdOptionAliases = ["--std", "-std"]; - private static readonly string[] s_testOutputOptionAliases = ["--test-output", "-to"]; - private static readonly string[] s_traverseOptionAliases = ["--traverse", "-t"]; - private static readonly string[] s_versionOptionAliases = ["--version", "-v"]; - private static readonly string[] s_withAccessSpecifierOptionAliases = ["--with-access-specifier", "-was"]; - private static readonly string[] s_withAttributeOptionAliases = ["--with-attribute", "-wa"]; - private static readonly string[] s_withCallConvOptionAliases = ["--with-callconv", "-wcc"]; - private static readonly string[] s_withClassOptionAliases = ["--with-class", "-wc"]; - private static readonly string[] s_withGuidOptionAliases = ["--with-guid", "-wg"]; - private static readonly string[] s_withLengthOptionAliases = ["--with-length", "-wl"]; - private static readonly string[] s_withLibraryPathOptionAliases = ["--with-librarypath", "-wlb"]; - private static readonly string[] s_withManualImportOptionAliases = ["--with-manual-import", "-wmi"]; - private static readonly string[] s_withNamespaceOptionAliases = ["--with-namespace", "-wn"]; - private static readonly string[] s_withPackingOptionAliases = ["--with-packing", "-wp"]; - private static readonly string[] s_withReadonlyOptionAliases = ["--with-readonly", "-wro"]; - private static readonly string[] s_withSetLastErrorOptionAliases = ["--with-setlasterror", "-wsle"]; - private static readonly string[] s_withSuppressGCTransitionOptionAliases = ["--with-suppressgctransition", "-wsgct"]; - private static readonly string[] s_withTransparentStructOptionAliases = ["--with-transparent-struct", "-wts"]; - private static readonly string[] s_withTypeOptionAliases = ["--with-type", "-wt"]; - private static readonly string[] s_withUsingOptionAliases = ["--with-using", "-wu"]; - - private static readonly Option s_additionalOption = GetAdditionalOption(); - private static readonly Option s_configOption = GetConfigOption(); - private static readonly Option s_defineMacros = GetDefineMacroOption(); - private static readonly Option s_excludedNames = GetExcludeOption(); - private static readonly Option s_files = GetFileOption(); - private static readonly Option s_fileDirectory = GetFileDirectoryOption(); - private static readonly Option s_headerFile = GetHeaderOption(); - private static readonly Option s_includedNames = GetIncludeOption(); - private static readonly Option s_includeDirectories = GetIncludeDirectoryOption(); - private static readonly Option s_language = GetLanguageOption(); - private static readonly Option s_libraryPath = GetLibraryOption(); - private static readonly Option s_methodClassName = GetMethodClassNameOption(); - private static readonly Option s_methodPrefixToStrip = GetPrefixStripOption(); - private static readonly Option s_namespaceName = GetNamespaceOption(); - private static readonly Option s_nativeTypeNamesToStrip = GetNativeTypeNamesStripOption(); - private static readonly Option s_outputLocation = GetOutputOption(); - private static readonly Option s_outputMode = GetOutputModeOption(); - private static readonly Option s_remappedNameValuePairs = GetRemapOption(); - private static readonly Option s_std = GetStdOption(); - private static readonly Option s_testOutputLocation = GetTestOutputOption(); - private static readonly Option s_traversalNames = GetTraverseOption(); - private static readonly Option s_versionOption = GetVersionOption(); - private static readonly Option s_withAccessSpecifierNameValuePairs = GetWithAccessSpecifierOption(); - private static readonly Option s_withAttributeNameValuePairs = GetWithAttributeOption(); - private static readonly Option s_withCallConvNameValuePairs = GetWithCallConvOption(); - private static readonly Option s_withClassNameValuePairs = GetWithClassOption(); - private static readonly Option s_withGuidNameValuePairs = GetWithGuidOption(); - private static readonly Option s_withLengthNameValuePairs = GetWithLengthOption(); - private static readonly Option s_withLibraryPathNameValuePairs = GetWithLibraryPathOption(); - private static readonly Option s_withManualImports = GetWithManualImportOption(); - private static readonly Option s_withNamespaceNameValuePairs = GetWithNamespaceOption(); - private static readonly Option s_withPackingNameValuePairs = GetWithPackingOption(); - private static readonly Option s_withReadonlys = GetWithReadonlyOption(); - private static readonly Option s_withSetLastErrors = GetWithSetLastErrorOption(); - private static readonly Option s_withSuppressGCTransitions = GetWithSuppressGCTransitionOption(); - private static readonly Option s_withTransparentStructNameValuePairs = GetWithTransparentStructOption(); - private static readonly Option s_withTypeNameValuePairs = GetWithTypeOption(); - private static readonly Option s_withUsingNameValuePairs = GetWithUsingOption(); - - private static readonly RootCommand s_rootCommand = GetRootCommand(); - - private static readonly TwoColumnHelpRow[] s_configOptions = - [ - new TwoColumnHelpRow("?, h, help", "Show help and usage information for -c, --config"), - - new TwoColumnHelpRow("", ""), - new TwoColumnHelpRow("# Codegen Options", ""), - new TwoColumnHelpRow("", ""), - - new TwoColumnHelpRow("compatible-codegen", "Bindings should be generated with .NET Standard 2.0 compatibility. Setting this disables preview code generation."), - new TwoColumnHelpRow("default-codegen", "Bindings should be generated for the current LTS version of .NET/C#. This is currently .NET 8/C# 12."), - new TwoColumnHelpRow("latest-codegen", "Bindings should be generated for the current STS version of .NET/C#. This is currently .NET 10/C# 14."), - new TwoColumnHelpRow("preview-codegen", "Bindings should be generated for the preview version of .NET/C#. This is currently .NET 10/C# 14."), - - new TwoColumnHelpRow("", ""), - new TwoColumnHelpRow("# File Options", ""), - new TwoColumnHelpRow("", ""), - - new TwoColumnHelpRow("single-file", "Bindings should be generated to a single output file. This is the default."), - new TwoColumnHelpRow("multi-file", "Bindings should be generated so there is approximately one type per file."), - - new TwoColumnHelpRow("", ""), - new TwoColumnHelpRow("# Type Options", ""), - new TwoColumnHelpRow("", ""), - - new TwoColumnHelpRow("unix-types", "Bindings should be generated assuming Unix defaults. This is the default on Unix platforms."), - new TwoColumnHelpRow("windows-types", "Bindings should be generated assuming Windows defaults. This is the default on Windows platforms."), - - new TwoColumnHelpRow("", ""), - new TwoColumnHelpRow("# Exclusion Options", ""), - new TwoColumnHelpRow("", ""), - - new TwoColumnHelpRow("exclude-anonymous-field-helpers", "The helper ref properties generated for fields in nested anonymous structs and unions should not be generated."), - new TwoColumnHelpRow("exclude-com-proxies", "Types recognized as COM proxies should not have bindings generated. These are currently function declarations ending with _UserFree, _UserMarshal, _UserSize, _UserUnmarshal, _Proxy, or _Stub."), - new TwoColumnHelpRow("exclude-default-remappings", "Default remappings for well known types should not be added. This currently includes intptr_t, ptrdiff_t, size_t, and uintptr_t"), - new TwoColumnHelpRow("exclude-empty-records", "Bindings for records that contain no members should not be generated. These are commonly encountered for opaque handle like types such as HWND."), - new TwoColumnHelpRow("exclude-enum-operators", "Bindings for operators over enum types should not be generated. These are largely unnecessary in C# as the operators are available by default."), - new TwoColumnHelpRow("exclude-fnptr-codegen", "Generated bindings for latest or preview codegen should not use function pointers."), - new TwoColumnHelpRow("exclude-funcs-with-body", "Bindings for functions with bodies should not be generated."), - new TwoColumnHelpRow("exclude-using-statics-for-enums", "Enum usages should be fully qualified and should not include a corresponding 'using static EnumName;'"), - - new TwoColumnHelpRow("", ""), - new TwoColumnHelpRow("# Vtbl Options", ""), - new TwoColumnHelpRow("", ""), - - new TwoColumnHelpRow("explicit-vtbls", "VTBLs should have an explicit type generated with named fields per entry."), - new TwoColumnHelpRow("implicit-vtbls", "VTBLs should be implicit to reduce metadata bloat. This is the current default"), - new TwoColumnHelpRow("trimmable-vtbls", "VTBLs should be defined but not used in helper methods to reduce metadata bloat when trimming."), - - new TwoColumnHelpRow("", ""), - new TwoColumnHelpRow("# Test Options", ""), - new TwoColumnHelpRow("", ""), - - new TwoColumnHelpRow("generate-tests-nunit", "Basic tests validating size, blittability, and associated metadata should be generated for NUnit."), - new TwoColumnHelpRow("generate-tests-xunit", "Basic tests validating size, blittability, and associated metadata should be generated for XUnit."), - - new TwoColumnHelpRow("", ""), - new TwoColumnHelpRow("# Generation Options", ""), - new TwoColumnHelpRow("", ""), - - new TwoColumnHelpRow("generate-aggressive-inlining", "[MethodImpl(MethodImplOptions.AggressiveInlining)] should be added to generated helper functions."), - new TwoColumnHelpRow("generate-callconv-member-function", "Instance function pointers should use [CallConvMemberFunction] where applicable."), - new TwoColumnHelpRow("generate-cpp-attributes", "[CppAttributeList(\"\")] should be generated to document the encountered C++ attributes."), - new TwoColumnHelpRow("generate-disable-runtime-marshalling", "[assembly: DisableRuntimeMarshalling] should be generated."), - new TwoColumnHelpRow("generate-doc-includes", " xml documentation tags should be generated for declarations."), - new TwoColumnHelpRow("generate-file-scoped-namespaces", "Namespaces should be scoped to the file to reduce nesting."), - new TwoColumnHelpRow("generate-guid-member", "Types with an associated GUID should have a corresponding member generated."), - new TwoColumnHelpRow("generate-helper-types", "Code files should be generated for various helper attributes and declared transparent structs."), - new TwoColumnHelpRow("generate-macro-bindings", "Bindings for macro-definitions should be generated. This currently only works with value like macros and not function-like ones."), - new TwoColumnHelpRow("generate-marker-interfaces", "Bindings for marker interfaces representing native inheritance hierarchies should be generated."), - new TwoColumnHelpRow("generate-native-bitfield-attribute", "[NativeBitfield(\"\", offset: #, length: #)] attribute should be generated to document the encountered bitfield layout."), - new TwoColumnHelpRow("generate-native-inheritance-attribute", "[NativeInheritance(\"\")] attribute should be generated to document the encountered C++ base type."), - new TwoColumnHelpRow("generate-generic-pointer-wrapper", "Pointer should be used for limited generic type support."), - new TwoColumnHelpRow("generate-setslastsystemerror-attribute", "[SetsLastSystemError] attribute should be generated rather than using SetLastError = true."), - new TwoColumnHelpRow("generate-template-bindings", "Bindings for template-definitions should be generated. This is currently experimental."), - new TwoColumnHelpRow("generate-unmanaged-constants", "Unmanaged constants should be generated using static ref readonly properties. This is currently experimental."), - new TwoColumnHelpRow("generate-vtbl-index-attribute", "[VtblIndex(#)] attribute should be generated to document the underlying VTBL index for a helper method."), - - new TwoColumnHelpRow("", ""), - new TwoColumnHelpRow("# Stripping Options", ""), - new TwoColumnHelpRow("", ""), - - new TwoColumnHelpRow("strip-enum-member-type-name", "Strips the enum type name from the beginning of its member names."), - - new TwoColumnHelpRow("", ""), - new TwoColumnHelpRow("# Logging Options", ""), - new TwoColumnHelpRow("", ""), - - new TwoColumnHelpRow("log-exclusions", "A list of excluded declaration types should be generated. This will also log if the exclusion was due to an exact or partial match."), - new TwoColumnHelpRow("log-potential-typedef-remappings", "A list of potential typedef remappings should be generated. This can help identify missing remappings."), - new TwoColumnHelpRow("log-visited-files", "A list of the visited files should be generated. This can help identify traversal issues."), - ]; - public static IEnumerable GetExtendedHelp(HelpContext context) { foreach (var sectionDelegate in HelpBuilder.Default.GetLayout()) @@ -989,446 +817,4 @@ private static void ParseKeyValuePairs(IEnumerable keyValuePairs, List GetAdditionalOption() - { - return new Option( - aliases: s_additionalOptionAliases, - description: "An argument to pass to Clang when parsing the input files.", - getDefaultValue: Array.Empty - ) { - AllowMultipleArgumentsPerToken = true - }; - } - - private static Option GetConfigOption() - { - return new Option( - aliases: s_configOptionAliases, - description: "A configuration option that controls how the bindings are generated. Specify 'help' to see the available options.", - getDefaultValue: Array.Empty - ) { - AllowMultipleArgumentsPerToken = true - }; - } - - private static Option GetDefineMacroOption() - { - return new Option( - aliases: s_defineMacroOptionAliases, - description: "Define to (or 1 if omitted).", - getDefaultValue: Array.Empty - ) { - AllowMultipleArgumentsPerToken = true - }; - } - - private static Option GetExcludeOption() - { - return new Option( - aliases: s_excludeOptionAliases, - description: "A declaration name to exclude from binding generation.", - getDefaultValue: Array.Empty - ) { - AllowMultipleArgumentsPerToken = true - }; - } - - private static Option GetFileOption() - { - return new Option( - aliases: s_fileOptionAliases, - description: "A file to parse and generate bindings for.", - getDefaultValue: Array.Empty - ) { - AllowMultipleArgumentsPerToken = true - }; - } - - private static Option GetFileDirectoryOption() - { - return new Option( - aliases: s_fileDirectionOptionAliases, - description: "The base path for files to parse.", - getDefaultValue: () => string.Empty - ); - } - - private static Option GetHeaderOption() - { - return new Option( - aliases: s_headerOptionAliases, - description: "A file which contains the header to prefix every generated file with.", - getDefaultValue: () => string.Empty - ); - } - - private static Option GetIncludeOption() - { - return new Option( - aliases: s_includeOptionAliases, - description: "A declaration name to include in binding generation.", - getDefaultValue: Array.Empty - ) { - AllowMultipleArgumentsPerToken = true - }; - } - - private static Option GetIncludeDirectoryOption() - { - return new Option( - aliases: s_includeDirectoryOptionAliases, - description: "Add directory to include search path.", - getDefaultValue: Array.Empty - ) { - AllowMultipleArgumentsPerToken = true - }; - } - - private static Option GetLanguageOption() - { - return new Option( - aliases: s_languageOptionAliases, - description: "Treat subsequent input files as having type .", - getDefaultValue: () => "c++" - ).FromAmong("c", "c++"); - } - - private static Option GetLibraryOption() - { - return new Option( - aliases: s_libraryOptionAliases, - description: "The string to use in the DllImport attribute used when generating bindings.", - getDefaultValue: () => string.Empty - ); - } - - private static Option GetMethodClassNameOption() - { - return new Option( - aliases: s_methodClassNameOptionAliases, - description: "The name of the static class that will contain the generated method bindings.", - getDefaultValue: () => "Methods" - ); - } - - private static Option GetNamespaceOption() - { - return new Option( - aliases: s_namespaceOptionAliases, - description: "The namespace in which to place the generated bindings.", - getDefaultValue: () => string.Empty - ); - } - - private static Option GetNativeTypeNamesStripOption() - { - return new Option( - aliases: s_nativeTypeNamesStripOptionAliases, - description: "The contents to strip from the generated NativeTypeName attributes.", - getDefaultValue: Array.Empty - ) { - AllowMultipleArgumentsPerToken = true - }; - } - - private static Option GetOutputModeOption() - { - return new Option( - aliases: s_outputModeOptionAliases, - description: "The mode describing how the information collected from the headers are presented in the resultant bindings.", - getDefaultValue: () => PInvokeGeneratorOutputMode.CSharp - ); - } - - private static Option GetOutputOption() - { - return new Option( - aliases: s_outputOptionAliases, - description: "The output location to write the generated bindings to.", - getDefaultValue: () => string.Empty - ); - } - - private static Option GetPrefixStripOption() - { - return new Option( - aliases: s_prefixStripOptionAliases, - description: "The prefix to strip from the generated method bindings.", - getDefaultValue: () => string.Empty - ); - } - - private static Option GetRemapOption() - { - return new Option( - aliases: s_remapOptionAliases, - description: "A declaration name to be remapped to another name during binding generation.", - getDefaultValue: Array.Empty - ) { - AllowMultipleArgumentsPerToken = true - }; - } - - private static RootCommand GetRootCommand() - { - var rootCommand = new RootCommand("ClangSharp P/Invoke Binding Generator") - { - s_additionalOption, - s_configOption, - s_defineMacros, - s_excludedNames, - s_files, - s_fileDirectory, - s_headerFile, - s_includedNames, - s_includeDirectories, - s_language, - s_libraryPath, - s_methodClassName, - s_namespaceName, - s_outputMode, - s_outputLocation, - s_methodPrefixToStrip, - s_nativeTypeNamesToStrip, - s_remappedNameValuePairs, - s_std, - s_testOutputLocation, - s_traversalNames, - s_versionOption, - s_withAccessSpecifierNameValuePairs, - s_withAttributeNameValuePairs, - s_withCallConvNameValuePairs, - s_withClassNameValuePairs, - s_withGuidNameValuePairs, - s_withLengthNameValuePairs, - s_withLibraryPathNameValuePairs, - s_withManualImports, - s_withNamespaceNameValuePairs, - s_withPackingNameValuePairs, - s_withReadonlys, - s_withSetLastErrors, - s_withSuppressGCTransitions, - s_withTransparentStructNameValuePairs, - s_withTypeNameValuePairs, - s_withUsingNameValuePairs - }; - Handler.SetHandler(rootCommand, (Action)Run); - return rootCommand; - } - - private static Option GetStdOption() - { - return new Option( - aliases: s_stdOptionAliases, - description: "Language standard to compile for.", - getDefaultValue: () => "" - ); - } - - private static Option GetTestOutputOption() - { - return new Option( - aliases: s_testOutputOptionAliases, - description: "The output location to write the generated tests to.", - getDefaultValue: () => string.Empty - ); - } - - private static Option GetVersionOption() - { - return new Option( - aliases: s_versionOptionAliases, - description: "Prints the current version information for the tool and its native dependencies." - ) { - Arity = ArgumentArity.Zero - }; - } - - private static Option GetTraverseOption() - { - return new Option( - aliases: s_traverseOptionAliases, - description: "A file name included either directly or indirectly by -f that should be traversed during binding generation.", - getDefaultValue: Array.Empty - ) { - AllowMultipleArgumentsPerToken = true - }; - } - - private static Option GetWithAccessSpecifierOption() - { - return new Option( - aliases: s_withAccessSpecifierOptionAliases, - description: "An access specifier to be used with the given qualified or remapped declaration name during binding generation. Supports wildcards.", - getDefaultValue: Array.Empty - ) { - AllowMultipleArgumentsPerToken = true - }; - } - - private static Option GetWithAttributeOption() - { - return new Option( - aliases: s_withAttributeOptionAliases, - description: "An attribute to be added to the given remapped declaration name during binding generation. Supports wildcards.", - getDefaultValue: Array.Empty - ) { - AllowMultipleArgumentsPerToken = true - }; - } - - private static Option GetWithCallConvOption() - { - return new Option( - aliases: s_withCallConvOptionAliases, - description: "A calling convention to be used for the given declaration during binding generation. Supports wildcards.", - getDefaultValue: Array.Empty - ) { - AllowMultipleArgumentsPerToken = true - }; - } - - private static Option GetWithClassOption() - { - return new Option( - aliases: s_withClassOptionAliases, - description: "A class to be used for the given remapped constant or function declaration name during binding generation. Supports wildcards.", - getDefaultValue: Array.Empty - ) { - AllowMultipleArgumentsPerToken = true - }; - } - - private static Option GetWithGuidOption() - { - return new Option( - aliases: s_withGuidOptionAliases, - description: "A GUID to be used for the given declaration during binding generation. Supports wildcards.", - getDefaultValue: Array.Empty - ) { - AllowMultipleArgumentsPerToken = true - }; - } - - private static Option GetWithLengthOption() - { - return new Option( - aliases: s_withLengthOptionAliases, - description: "A length to be used for the given declaration during binding generation. Supports wildcards.", - getDefaultValue: Array.Empty - ) { - AllowMultipleArgumentsPerToken = true - }; - } - - private static Option GetWithLibraryPathOption() - { - return new Option( - aliases: s_withLibraryPathOptionAliases, - description: "A library path to be used for the given declaration during binding generation. Supports wildcards.", - getDefaultValue: Array.Empty - ) { - AllowMultipleArgumentsPerToken = true - }; - } - - private static Option GetWithManualImportOption() - { - return new Option( - aliases: s_withManualImportOptionAliases, - description: "A remapped function name to be treated as a manual import during binding generation. Supports wildcards.", - getDefaultValue: Array.Empty - ) { - AllowMultipleArgumentsPerToken = true - }; - } - - private static Option GetWithNamespaceOption() - { - return new Option( - aliases: s_withNamespaceOptionAliases, - description: "A namespace to be used for the given remapped declaration name during binding generation. Supports wildcards.", - getDefaultValue: Array.Empty - ) { - AllowMultipleArgumentsPerToken = true - }; - } - - private static Option GetWithReadonlyOption() - { - return new Option( - aliases: s_withReadonlyOptionAliases, - description: "Add the readonly modifier to a given instance method. Supports wildcards.", - getDefaultValue: Array.Empty - ) { - AllowMultipleArgumentsPerToken = true - }; - } - - private static Option GetWithSetLastErrorOption() - { - return new Option( - aliases: s_withSetLastErrorOptionAliases, - description: "Add the SetLastError=true modifier or SetsSystemLastError attribute to a given DllImport or UnmanagedFunctionPointer. Supports wildcards.", - getDefaultValue: Array.Empty - ) { - AllowMultipleArgumentsPerToken = true - }; - } - - private static Option GetWithSuppressGCTransitionOption() - { - return new Option( - aliases: s_withSuppressGCTransitionOptionAliases, - description: "Add the SuppressGCTransition calling convention to a given DllImport or UnmanagedFunctionPointer. Supports wildcards.", - getDefaultValue: Array.Empty - ) { - AllowMultipleArgumentsPerToken = true - }; - } - - private static Option GetWithTransparentStructOption() - { - return new Option( - aliases: s_withTransparentStructOptionAliases, - description: "A remapped type name to be treated as a transparent wrapper during binding generation. Supports wildcards.", - getDefaultValue: Array.Empty - ) { - AllowMultipleArgumentsPerToken = true - }; - } - - private static Option GetWithTypeOption() - { - return new Option( - aliases: s_withTypeOptionAliases, - description: "A type to be used for the given enum declaration during binding generation. Supports wildcards.", - getDefaultValue: Array.Empty - ) { - AllowMultipleArgumentsPerToken = true - }; - } - - private static Option GetWithUsingOption() - { - return new Option( - aliases: s_withUsingOptionAliases, - description: "A using directive to be included for the given remapped declaration name during binding generation. Supports wildcards.", - getDefaultValue: Array.Empty - ) { - AllowMultipleArgumentsPerToken = true - }; - } - - private static Option GetWithPackingOption() - { - return new Option( - aliases: s_withPackingOptionAliases, - description: "Overrides the StructLayoutAttribute.Pack property for the given type. Supports wildcards.", - getDefaultValue: Array.Empty - ) { - AllowMultipleArgumentsPerToken = true - }; - } }