diff --git a/src/Microsoft.Windows.CsWin32/Generator.cs b/src/Microsoft.Windows.CsWin32/Generator.cs index 707c5d54..5fd02acb 100644 --- a/src/Microsoft.Windows.CsWin32/Generator.cs +++ b/src/Microsoft.Windows.CsWin32/Generator.cs @@ -61,8 +61,6 @@ public class Generator : IDisposable { "CloseHandle", ParseTypeName("Microsoft.Win32.SafeHandles.SafeFileHandle").WithAdditionalAnnotations(IsManagedTypeAnnotation, IsSafeHandleTypeAnnotation) }, }; - private const string CommonNamespaceDot = "Windows.Win32."; - private const string CommonNamespace = "Windows.Win32"; private const string SystemRuntimeCompilerServices = "System.Runtime.CompilerServices"; private const string SystemRuntimeInteropServices = "System.Runtime.InteropServices"; private const string NativeTypedefAttribute = "NativeTypedefAttribute"; @@ -537,7 +535,7 @@ public bool TryGenerateNamespace(string @namespace, out IReadOnlyList pr if (!this.MetadataIndex.MetadataByNamespace.TryGetValue(@namespace, out metadata)) { // Fallback to case insensitive search if it looks promising to do so. - if (@namespace.StartsWith(CommonNamespace, StringComparison.OrdinalIgnoreCase)) + if (@namespace.StartsWith(this.MetadataIndex.CommonNamespace, StringComparison.OrdinalIgnoreCase)) { foreach (var item in this.MetadataIndex.MetadataByNamespace) { @@ -1019,7 +1017,7 @@ nsContents.Key is object usingDirectives.Add(UsingDirective(ParseName(GlobalNamespacePrefix + "System.Runtime.Versioning"))); } - usingDirectives.Add(UsingDirective(NameEquals(GlobalWin32NamespaceAlias), ParseName(GlobalNamespacePrefix + CommonNamespace))); + usingDirectives.Add(UsingDirective(NameEquals(GlobalWin32NamespaceAlias), ParseName(GlobalNamespacePrefix + this.MetadataIndex.CommonNamespace))); var normalizedResults = new Dictionary(StringComparer.OrdinalIgnoreCase); results.AsParallel().WithCancellation(cancellationToken).ForAll(kv => @@ -1083,25 +1081,6 @@ nsContents.Key is object internal static TypeSyntax MakeReadOnlySpanOfT(TypeSyntax typeArgument) => GenericName("ReadOnlySpan").AddTypeArgumentListArguments(typeArgument); - internal static string ReplaceCommonNamespaceWithAlias(string fullNamespace) => TryStripCommonNamespace(fullNamespace, out string? stripped) ? $"{GlobalWin32NamespaceAlias}.{stripped}" : fullNamespace; - - internal static bool TryStripCommonNamespace(string fullNamespace, [NotNullWhen(true)] out string? strippedNamespace) - { - if (fullNamespace.StartsWith(CommonNamespaceDot, StringComparison.Ordinal)) - { - strippedNamespace = fullNamespace.Substring(CommonNamespaceDot.Length); - return true; - } - else if (fullNamespace == CommonNamespace) - { - strippedNamespace = string.Empty; - return true; - } - - strippedNamespace = null; - return false; - } - /// /// Checks whether an exception was originally thrown because of a target platform incompatibility. /// @@ -1119,6 +1098,28 @@ internal static bool IsPlatformCompatibleException(Exception? ex) internal static bool IsUntypedDelegate(MetadataReader reader, TypeDefinition typeDef) => reader.StringComparer.Equals(typeDef.Name, "PROC") || reader.StringComparer.Equals(typeDef.Name, "FARPROC"); + internal static string ReplaceCommonNamespaceWithAlias(Generator? generator, string fullNamespace) + { + return generator is object && generator.TryStripCommonNamespace(fullNamespace, out string? stripped) ? $"{GlobalWin32NamespaceAlias}.{stripped}" : fullNamespace; + } + + internal bool TryStripCommonNamespace(string fullNamespace, [NotNullWhen(true)] out string? strippedNamespace) + { + if (fullNamespace.StartsWith(this.MetadataIndex.CommonNamespaceDot, StringComparison.Ordinal)) + { + strippedNamespace = fullNamespace.Substring(this.MetadataIndex.CommonNamespaceDot.Length); + return true; + } + else if (fullNamespace == this.MetadataIndex.CommonNamespace) + { + strippedNamespace = string.Empty; + return true; + } + + strippedNamespace = null; + return false; + } + internal bool IsAttribute(CustomAttribute attribute, string ns, string name) => MetadataUtilities.IsAttribute(this.Reader, attribute, ns, name); internal bool TryGetHandleReleaseMethod(EntityHandle handleStructDefHandle, [NotNullWhen(true)] out string? releaseMethod) @@ -1292,7 +1293,7 @@ internal void RequestInteropType(TypeDefinitionHandle typeDefHandle) { if (this.RequestInteropType(typeDefHandle, null) is MemberDeclarationSyntax typeDeclaration) { - if (!TryStripCommonNamespace(ns, out string? shortNamespace)) + if (!this.TryStripCommonNamespace(ns, out string? shortNamespace)) { throw new GenerationFailedException("Unexpected namespace: " + ns); } @@ -3085,7 +3086,7 @@ private TypeDeclarationSyntax DeclareInterfaceAsStruct(TypeDefinition typeDef, S methodDeclaration = this.AddApiDocumentation($"{ifaceName}.{methodName}", methodDeclaration); members.Add(methodDeclaration); - NameSyntax declaringTypeName = HandleTypeHandleInfo.GetNestingQualifiedName(this.Reader, typeDef); + NameSyntax declaringTypeName = HandleTypeHandleInfo.GetNestingQualifiedName(this, this.Reader, typeDef); friendlyOverloads.AddRange( this.DeclareFriendlyOverloads(methodDefinition, methodDeclaration, declaringTypeName, FriendlyOverloadOf.InterfaceMethod)); } diff --git a/src/Microsoft.Windows.CsWin32/HandleTypeHandleInfo.cs b/src/Microsoft.Windows.CsWin32/HandleTypeHandleInfo.cs index 9734891c..04d68d0a 100644 --- a/src/Microsoft.Windows.CsWin32/HandleTypeHandleInfo.cs +++ b/src/Microsoft.Windows.CsWin32/HandleTypeHandleInfo.cs @@ -45,14 +45,14 @@ internal override TypeSyntaxAndMarshaling ToTypeSyntax(TypeSyntaxSettings inputs { case HandleKind.TypeDefinition: TypeDefinition td = this.reader.GetTypeDefinition((TypeDefinitionHandle)this.Handle); - nameSyntax = inputs.QualifyNames ? GetNestingQualifiedName(this.reader, td) : IdentifierName(this.reader.GetString(td.Name)); + nameSyntax = inputs.QualifyNames ? GetNestingQualifiedName(inputs.Generator, this.reader, td) : IdentifierName(this.reader.GetString(td.Name)); isInterface = (td.Attributes & TypeAttributes.Interface) == TypeAttributes.Interface; isNonCOMConformingInterface = isInterface && inputs.Generator?.IsNonCOMInterface(td) is true; break; case HandleKind.TypeReference: var trh = (TypeReferenceHandle)this.Handle; TypeReference tr = this.reader.GetTypeReference(trh); - nameSyntax = inputs.QualifyNames ? GetNestingQualifiedName(this.reader, tr) : IdentifierName(this.reader.GetString(tr.Name)); + nameSyntax = inputs.QualifyNames ? GetNestingQualifiedName(inputs.Generator, this.reader, tr) : IdentifierName(this.reader.GetString(tr.Name)); isInterface = inputs.Generator?.IsInterface(trh) is true; isNonCOMConformingInterface = isInterface && inputs.Generator?.IsNonCOMInterface(trh) is true; break; @@ -82,7 +82,7 @@ internal override TypeSyntaxAndMarshaling ToTypeSyntax(TypeSyntaxSettings inputs if (inputs.Generator is object) { inputs.Generator.RequestSpecialTypeDefStruct(specialName, out string fullyQualifiedName); - return new TypeSyntaxAndMarshaling(ParseName(Generator.ReplaceCommonNamespaceWithAlias(fullyQualifiedName))); + return new TypeSyntaxAndMarshaling(ParseName(Generator.ReplaceCommonNamespaceWithAlias(inputs.Generator, fullyQualifiedName))); } else { @@ -154,24 +154,24 @@ private static bool TryMarshalAsObject(TypeSyntaxSettings inputs, string name, [ return false; } - private static NameSyntax GetNestingQualifiedName(MetadataReader reader, TypeDefinitionHandle handle) => GetNestingQualifiedName(reader, reader.GetTypeDefinition(handle)); + private static NameSyntax GetNestingQualifiedName(Generator? generator, MetadataReader reader, TypeDefinitionHandle handle) => GetNestingQualifiedName(generator, reader, reader.GetTypeDefinition(handle)); - internal static NameSyntax GetNestingQualifiedName(MetadataReader reader, TypeDefinition td) + internal static NameSyntax GetNestingQualifiedName(Generator? generator, MetadataReader reader, TypeDefinition td) { IdentifierNameSyntax name = IdentifierName(reader.GetString(td.Name)); return td.GetDeclaringType() is { IsNil: false } nestingType - ? QualifiedName(GetNestingQualifiedName(reader, nestingType), name) - : QualifiedName(ParseName(Generator.ReplaceCommonNamespaceWithAlias(reader.GetString(td.Namespace))), name); + ? QualifiedName(GetNestingQualifiedName(generator, reader, nestingType), name) + : QualifiedName(ParseName(Generator.ReplaceCommonNamespaceWithAlias(generator, reader.GetString(td.Namespace))), name); } - private static NameSyntax GetNestingQualifiedName(MetadataReader reader, TypeReferenceHandle handle) => GetNestingQualifiedName(reader, reader.GetTypeReference(handle)); + private static NameSyntax GetNestingQualifiedName(Generator? generator, MetadataReader reader, TypeReferenceHandle handle) => GetNestingQualifiedName(generator, reader, reader.GetTypeReference(handle)); - private static NameSyntax GetNestingQualifiedName(MetadataReader reader, TypeReference tr) + private static NameSyntax GetNestingQualifiedName(Generator? generator, MetadataReader reader, TypeReference tr) { SimpleNameSyntax typeName = IdentifierName(reader.GetString(tr.Name)); return tr.ResolutionScope.Kind == HandleKind.TypeReference - ? QualifiedName(GetNestingQualifiedName(reader, (TypeReferenceHandle)tr.ResolutionScope), typeName) - : QualifiedName(ParseName(Generator.ReplaceCommonNamespaceWithAlias(reader.GetString(tr.Namespace))), typeName); + ? QualifiedName(GetNestingQualifiedName(generator, reader, (TypeReferenceHandle)tr.ResolutionScope), typeName) + : QualifiedName(ParseName(Generator.ReplaceCommonNamespaceWithAlias(generator, reader.GetString(tr.Namespace))), typeName); } private bool IsDelegate(TypeSyntaxSettings inputs, out TypeDefinition delegateTypeDef) diff --git a/src/Microsoft.Windows.CsWin32/MetadataIndex.cs b/src/Microsoft.Windows.CsWin32/MetadataIndex.cs index e0e10f54..dd0f3aa4 100644 --- a/src/Microsoft.Windows.CsWin32/MetadataIndex.cs +++ b/src/Microsoft.Windows.CsWin32/MetadataIndex.cs @@ -8,6 +8,7 @@ namespace Microsoft.Windows.CsWin32 using System.Collections.ObjectModel; using System.Diagnostics; using System.IO; + using System.Linq; using System.Reflection; using System.Reflection.Metadata; using System.Reflection.PortableExecutable; @@ -83,7 +84,6 @@ void PopulateNamespace(NamespaceDefinition ns, string? parentNamespace) string nsFullName = string.IsNullOrEmpty(parentNamespace) ? nsLeafName : $"{parentNamespace}.{nsLeafName}"; var nsMetadata = new NamespaceMetadata(nsFullName); - this.MetadataByNamespace.Add(nsFullName, nsMetadata); foreach (TypeDefinitionHandle tdh in ns.TypeDefinitions) { @@ -163,6 +163,11 @@ void PopulateNamespace(NamespaceDefinition ns, string? parentNamespace) } } + if (!nsMetadata.IsEmpty) + { + this.MetadataByNamespace.Add(nsFullName, nsMetadata); + } + foreach (NamespaceDefinitionHandle childNsHandle in ns.NamespaceDefinitions) { PopulateNamespace(this.mr.GetNamespaceDefinition(childNsHandle), nsFullName); @@ -173,6 +178,17 @@ void PopulateNamespace(NamespaceDefinition ns, string? parentNamespace) { PopulateNamespace(this.mr.GetNamespaceDefinitionRoot(), parentNamespace: null); } + + this.CommonNamespace = CommonPrefix(this.MetadataByNamespace.Keys.ToList()); + if (this.CommonNamespace[this.CommonNamespace.Length - 1] == '.') + { + this.CommonNamespaceDot = this.CommonNamespace; + this.CommonNamespace = this.CommonNamespace.Substring(0, this.CommonNamespace.Length - 1); + } + else + { + this.CommonNamespaceDot = this.CommonNamespace + "."; + } } catch { @@ -200,6 +216,10 @@ void PopulateNamespace(NamespaceDefinition ns, string? parentNamespace) internal IReadOnlyDictionary HandleTypeReleaseMethod => this.handleTypeReleaseMethod; + internal string CommonNamespace { get; } + + internal string CommonNamespaceDot { get; } + private string DebuggerDisplay => $"{this.metadataPath} ({this.platform})"; public void Dispose() @@ -236,6 +256,36 @@ internal static void Return(MetadataIndex index) } } + private static string CommonPrefix(IReadOnlyList ss) + { + if (ss.Count == 0) + { + return string.Empty; + } + + if (ss.Count == 1) + { + return ss[0]; + } + + int prefixLength = 0; + + foreach (char c in ss[0]) + { + foreach (string s in ss) + { + if (s.Length <= prefixLength || s[prefixLength] != c) + { + return ss[0].Substring(0, prefixLength); + } + } + + prefixLength++; + } + + return ss[0]; // all strings identical up to length of ss[0] + } + [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] private struct CacheKey : IEquatable { diff --git a/src/Microsoft.Windows.CsWin32/NamespaceMetadata.cs b/src/Microsoft.Windows.CsWin32/NamespaceMetadata.cs index b3041d89..b2849913 100644 --- a/src/Microsoft.Windows.CsWin32/NamespaceMetadata.cs +++ b/src/Microsoft.Windows.CsWin32/NamespaceMetadata.cs @@ -18,6 +18,8 @@ internal NamespaceMetadata(string name) public string Name { get; } + public bool IsEmpty => this.Fields.Count == 0 && this.Methods.Count == 0 && this.Types.Count == 0; + internal Dictionary Fields { get; } = new(StringComparer.Ordinal); internal Dictionary Methods { get; } = new(StringComparer.Ordinal);