Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix generation of interop types from multiple winmd's #418

Merged
merged 1 commit into from
Sep 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 26 additions & 25 deletions src/Microsoft.Windows.CsWin32/Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -537,7 +535,7 @@ public bool TryGenerateNamespace(string @namespace, out IReadOnlyList<string> 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)
{
Expand Down Expand Up @@ -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<string, CompilationUnitSyntax>(StringComparer.OrdinalIgnoreCase);
results.AsParallel().WithCancellation(cancellationToken).ForAll(kv =>
Expand Down Expand Up @@ -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;
}

/// <summary>
/// Checks whether an exception was originally thrown because of a target platform incompatibility.
/// </summary>
Expand All @@ -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)
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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));
}
Expand Down
22 changes: 11 additions & 11 deletions src/Microsoft.Windows.CsWin32/HandleTypeHandleInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
{
Expand Down Expand Up @@ -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)
Expand Down
52 changes: 51 additions & 1 deletion src/Microsoft.Windows.CsWin32/MetadataIndex.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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);
Expand All @@ -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
{
Expand Down Expand Up @@ -200,6 +216,10 @@ void PopulateNamespace(NamespaceDefinition ns, string? parentNamespace)

internal IReadOnlyDictionary<TypeDefinitionHandle, string> HandleTypeReleaseMethod => this.handleTypeReleaseMethod;

internal string CommonNamespace { get; }

internal string CommonNamespaceDot { get; }

private string DebuggerDisplay => $"{this.metadataPath} ({this.platform})";

public void Dispose()
Expand Down Expand Up @@ -236,6 +256,36 @@ internal static void Return(MetadataIndex index)
}
}

private static string CommonPrefix(IReadOnlyList<string> 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<CacheKey>
{
Expand Down
2 changes: 2 additions & 0 deletions src/Microsoft.Windows.CsWin32/NamespaceMetadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, FieldDefinitionHandle> Fields { get; } = new(StringComparer.Ordinal);

internal Dictionary<string, MethodDefinitionHandle> Methods { get; } = new(StringComparer.Ordinal);
Expand Down