Skip to content
This repository has been archived by the owner on Jan 11, 2024. It is now read-only.

Only add a type forward if the nested type is visible outside of the assembly #275

Merged
87 changes: 52 additions & 35 deletions src/GenFacades/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -312,41 +312,70 @@ private static IEnumerable<string> EnumerateDocIdsToForward(IAssembly contractAs

var typesToForward = contractAssembly.GetAllTypes().Where(t => TypeHelper.IsVisibleOutsideAssembly(t))
.OfType<INamespaceTypeDefinition>();
List<string> result = typeForwardsToForward.Concat(typesToForward)
.Select(type => TypeHelper.GetTypeName(type, NameFormattingOptions.DocumentationId)).ToList();

foreach(var type in typesToForward)
{
AddNestedTypeDocIds(result, type);
}

return typeForwardsToForward.Concat(typesToForward)
.Select(type => TypeHelper.GetTypeName(type, NameFormattingOptions.DocumentationId));
return result;
}

private static void AddNestedTypeDocIds(List<string> docIds, INamedTypeDefinition type)
{
foreach (var nestedType in type.NestedTypes)
{
if (TypeHelper.IsVisibleOutsideAssembly(nestedType))
docIds.Add(TypeHelper.GetTypeName(nestedType, NameFormattingOptions.DocumentationId));
AddNestedTypeDocIds(docIds, nestedType);
}
}

private static IReadOnlyDictionary<string, IReadOnlyList<INamespaceTypeDefinition>> GenerateTypeTable(IEnumerable<IAssembly> seedAssemblies)
private static IReadOnlyDictionary<string, IReadOnlyList<INamedTypeDefinition>> GenerateTypeTable(IEnumerable<IAssembly> seedAssemblies)
{
var typeTable = new Dictionary<string, IReadOnlyList<INamespaceTypeDefinition>>();
var typeTable = new Dictionary<string, IReadOnlyList<INamedTypeDefinition>>();
foreach (var assembly in seedAssemblies)
{
foreach (var type in assembly.GetAllTypes().OfType<INamespaceTypeDefinition>())
foreach (var type in assembly.GetAllTypes().OfType<INamedTypeDefinition>())
{
if (!TypeHelper.IsVisibleOutsideAssembly(type))
continue;
AddTypeAndNestedTypesToTable(typeTable, type);
}
}
return typeTable;
}

IReadOnlyList<INamespaceTypeDefinition> seedTypes;
string docId = TypeHelper.GetTypeName(type, NameFormattingOptions.DocumentationId);
if (!typeTable.TryGetValue(docId, out seedTypes))
{
seedTypes = new List<INamespaceTypeDefinition>(1);
typeTable.Add(docId, seedTypes);
}
private static void AddTypeAndNestedTypesToTable(Dictionary<string, IReadOnlyList<INamedTypeDefinition>> typeTable, INamedTypeDefinition type)
{
if (type != null)
{
IReadOnlyList<INamedTypeDefinition> seedTypes;
string docId = TypeHelper.GetTypeName(type, NameFormattingOptions.DocumentationId);
if (!typeTable.TryGetValue(docId, out seedTypes))
{
seedTypes = new List<INamedTypeDefinition>(1);
typeTable.Add(docId, seedTypes);
}
if (!seedTypes.Contains(type))
((List<INamedTypeDefinition>)seedTypes).Add(type);

((List<INamespaceTypeDefinition>)seedTypes).Add(type);
foreach (INestedTypeDefinition nestedType in type.NestedTypes)
{
if (TypeHelper.IsVisibleOutsideAssembly(nestedType))
AddTypeAndNestedTypesToTable(typeTable, nestedType);
}
}
return typeTable;
}

private class FacadeGenerator
{
private readonly IMetadataHost _seedHost;
private readonly IMetadataHost _contractHost;
private readonly IReadOnlyDictionary<string, IEnumerable<string>> _docIdTable;
private readonly IReadOnlyDictionary<string, IReadOnlyList<INamespaceTypeDefinition>> _typeTable;
private readonly IReadOnlyDictionary<string, IReadOnlyList<INamedTypeDefinition>> _typeTable;
private readonly IReadOnlyDictionary<string, string> _seedTypePreferences;
private readonly bool _clearBuildAndRevision;
private readonly bool _buildDesignTimeFacades;
Expand All @@ -356,7 +385,7 @@ public FacadeGenerator(
IMetadataHost seedHost,
IMetadataHost contractHost,
IReadOnlyDictionary<string, IEnumerable<string>> docIdTable,
IReadOnlyDictionary<string, IReadOnlyList<INamespaceTypeDefinition>> typeTable,
IReadOnlyDictionary<string, IReadOnlyList<INamedTypeDefinition>> typeTable,
IReadOnlyDictionary<string, string> seedTypePreferences,
bool clearBuildAndRevision,
bool buildDesignTimeFacades,
Expand Down Expand Up @@ -398,7 +427,7 @@ public Assembly GenerateFacade(IAssembly contractAssembly, IAssemblyReference se
IEnumerable<string> missingDocIds = docIds.Where(id => !existingDocIds.Contains(id));
foreach (string docId in missingDocIds)
{
IReadOnlyList<INamespaceTypeDefinition> seedTypes;
IReadOnlyList<INamedTypeDefinition> seedTypes;
if (!_typeTable.TryGetValue(docId, out seedTypes))
{
if (!ignoreMissingTypes)
Expand All @@ -409,7 +438,7 @@ public Assembly GenerateFacade(IAssembly contractAssembly, IAssemblyReference se
continue;
}

INamespaceTypeDefinition seedType = GetSeedType(docId, seedTypes);
INamedTypeDefinition seedType = GetSeedType(docId, seedTypes);
if (seedType == null)
{
TraceDuplicateSeedTypeError(docId, seedTypes);
Expand Down Expand Up @@ -447,7 +476,7 @@ public Assembly GenerateFacade(IAssembly contractAssembly, IAssemblyReference se
return assembly;
}

private INamespaceTypeDefinition GetSeedType(string docId, IReadOnlyList<INamespaceTypeDefinition> seedTypes)
private INamedTypeDefinition GetSeedType(string docId, IReadOnlyList<INamedTypeDefinition> seedTypes)
{
Debug.Assert(seedTypes.Count != 0); // we should already have checked for non-existent types.

Expand All @@ -459,19 +488,19 @@ private INamespaceTypeDefinition GetSeedType(string docId, IReadOnlyList<INamesp
string preferredSeedAssembly;
if (_seedTypePreferences.TryGetValue(docId, out preferredSeedAssembly))
{
return seedTypes.SingleOrDefault(t => String.Equals(t.ContainingUnitNamespace.Unit.Name.Value, preferredSeedAssembly, StringComparison.OrdinalIgnoreCase));
return seedTypes.SingleOrDefault(t => String.Equals(t.GetAssembly().Name.Value, preferredSeedAssembly, StringComparison.OrdinalIgnoreCase));
}

return null;
}

private static void TraceDuplicateSeedTypeError(string docId, IReadOnlyList<INamespaceTypeDefinition> seedTypes)
private static void TraceDuplicateSeedTypeError(string docId, IReadOnlyList<INamedTypeDefinition> seedTypes)
{
Trace.TraceError("The type '{0}' is defined in multiple seed assemblies. If this is intentional, specify one of the following arguments to choose the preferred seed type:", docId);

foreach (INamespaceTypeDefinition type in seedTypes)
foreach (INamedTypeDefinition type in seedTypes)
{
Trace.TraceError(" /preferSeedType:{0}={1}", docId.Substring("T:".Length), type.ContainingUnitNamespace.Unit.Name.Value);
Trace.TraceError(" /preferSeedType:{0}={1}", docId.Substring("T:".Length), type.GetAssembly().Name.Value);
}
}

Expand All @@ -484,18 +513,6 @@ private void AddTypeForward(Assembly assembly, INamedTypeDefinition seedType)
if (assembly.ExportedTypes == null)
assembly.ExportedTypes = new List<IAliasForType>();
assembly.ExportedTypes.Add(alias);

// Recursively add forwarders for all nested types regardless of their accessibility. This is
// how the C# compiler emits type forwarders for nested types. We might not need them for
// nested types that are not visible outside the assembly, but it is safer to replicate how
// the C# compiler works. Plus, it helps when diffing the output from ildasm for facades
// that were built from source vs. those that were produced by this tool.
//
// NOTE: Some design-time tools can resolve forwarded nested types with only the top-level forwarder,
// but the runtime currently throws a TypeLoadException without explicit forwarders for the nested
// types.
foreach (var nestedType in seedType.NestedTypes.OrderBy(t => t.Name.Value))
AddTypeForward(assembly, nestedType);
}

private void AddWin32VersionResource(string contractLocation, Assembly facade)
Expand Down