Skip to content
Draft
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
5 changes: 4 additions & 1 deletion src/Microsoft.VisualStudio.Composition/ComposedPart.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,10 @@ public IEnumerable<ComposedPartDiagnostic> Validate(IReadOnlyDictionary<Type, Ex
}

var importsWithGenericTypeParameters = this.Definition.Imports
.Where(import => import.ImportingSiteElementTypeRef.GenericTypeParameterCount != 0).ToList();
.Where(import =>
import.ImportingSiteElementTypeRef.GenericTypeParameterCount != 0 &&
!import.ImportDefinition.Metadata.ContainsKey(CompositionConstants.GenericParameterIndexesMetadataName))
.ToList();
foreach (var import in importsWithGenericTypeParameters)
{
yield return new ComposedPartDiagnostic(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ public static class CompositionConstants

internal const string IsOpenGenericExport = MefV3CompositionNamespace + ".IsOpenGenericExport";

// Stores int[] of type-parameter positions (within the declaring open-generic type) for each
// type argument of a parameterized generic import (e.g. IOptionsFactory<TOptions> → [0]).
// Present when the import is a generic type parameterized by the declaring type's own parameters.
internal const string GenericParameterIndexesMetadataName = MefV3CompositionNamespace + ".GenericParameterIndexes";

// ExportFactory<T> support (V3-specific)
internal const string ExportFactoryProductImportDefinition = MefV3CompositionNamespace + ".ProductImportDefinition";
internal const string ExportFactoryTypeMetadataName = MefV3CompositionNamespace + ".ExportFactoryType";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -356,13 +356,41 @@ private bool TryCreateImportDefinition(Type importingType, ICustomAttributeProvi
contractType = contractType.GetTypeInfo().GetGenericArguments()[0];
}

// Detect parameterized generic imports on open-generic parts:
// e.g. IOptionsFactory<TOptions> where TOptions is the part's own type parameter.
// Switch the contract to the open generic and record the parameter index positions so
// the runtime can close the import with the concrete type arguments.
int[]? genericParamIndexes = null;
if (contractType.IsConstructedGenericType)
{
var typeArgs = contractType.GetGenericArguments();
if (typeArgs.All(a => a.IsGenericParameter))
{
genericParamIndexes = typeArgs.Select(a => a.GenericParameterPosition).ToArray();
contractType = contractType.GetGenericTypeDefinition();
}
}

importConstraints = importConstraints
.Union(this.GetMetadataViewConstraints(importingType, importMany: false))
.Union(GetExportTypeIdentityConstraints(contractType));
.Union(this.GetMetadataViewConstraints(importingType, importMany: false));

// Only add the type-identity constraint for normal (non-generic-parameter) imports.
// For parameterized generic imports the contract name is the open generic definition,
// and its exports carry a format-string identity that would not match a static constraint.
if (genericParamIndexes is null)
{
importConstraints = importConstraints.Union(GetExportTypeIdentityConstraints(contractType));
}

var importMetadata = genericParamIndexes is not null
? ImmutableDictionary.Create<string, object?>()
.Add(CompositionConstants.GenericParameterIndexesMetadataName, genericParamIndexes)
: GetImportMetadataForGenericTypeImport(contractType);

importDefinition = new ImportDefinition(
string.IsNullOrEmpty(importAttribute.ContractName) ? GetContractName(contractType) : importAttribute.ContractName,
importAttribute.AllowDefault ? ImportCardinality.OneOrZero : ImportCardinality.ExactlyOne,
GetImportMetadataForGenericTypeImport(contractType),
importMetadata,
importConstraints,
sharingBoundaries);
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -306,13 +306,34 @@ private bool TryCreateImportDefinition(Type importingType, ICustomAttributeProvi
: (CreationPolicy)importAttribute.RequiredCreationPolicy;

Type contractType = importAttribute.ContractType ?? GetTypeIdentityFromImportingType(importingType, importMany: false);

int[]? genericParamIndexes = null;
if (contractType.IsConstructedGenericType)
{
var typeArgs = contractType.GetGenericArguments();
if (typeArgs.All(a => a.IsGenericParameter))
{
genericParamIndexes = typeArgs.Select(a => a.GenericParameterPosition).ToArray();
contractType = contractType.GetGenericTypeDefinition();
}
}

var constraints = PartCreationPolicyConstraint.GetRequiredCreationPolicyConstraints(requiredCreationPolicy)
.Union(this.GetMetadataViewConstraints(importingType, importMany: false))
.Union(GetExportTypeIdentityConstraints(contractType));
.Union(this.GetMetadataViewConstraints(importingType, importMany: false));
if (genericParamIndexes is null)
{
constraints = constraints.Union(GetExportTypeIdentityConstraints(contractType));
}

var importMetadata = genericParamIndexes is not null
? ImmutableDictionary.Create<string, object?>()
.Add(CompositionConstants.GenericParameterIndexesMetadataName, genericParamIndexes)
: GetImportMetadataForGenericTypeImport(contractType);

importDefinition = new ImportDefinition(
string.IsNullOrEmpty(importAttribute.ContractName) ? GetContractName(contractType) : importAttribute.ContractName!,
importAttribute.AllowDefault ? ImportCardinality.OneOrZero : ImportCardinality.ExactlyOne,
GetImportMetadataForGenericTypeImport(contractType),
importMetadata,
constraints);
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ private TypeRef(Resolver resolver, Type type, bool shallow = false)
this.FullName = (arrayElementType.GetTypeInfo().IsGenericType ? arrayElementType.GetGenericTypeDefinition() : arrayElementType).FullName ?? throw Assumes.NotReachable();
this.GenericTypeParameterCount = arrayElementType.GetTypeInfo().GenericTypeParameters.Length;
this.GenericTypeArguments = arrayElementType.GenericTypeArguments != null && arrayElementType.GenericTypeArguments.Length > 0
? arrayElementType.GenericTypeArguments.Where(t => !(shallow && t.IsGenericParameter)).Select(t => new TypeRef(resolver, t, shallow: true)).ToImmutableArray()
? arrayElementType.GenericTypeArguments.Where(t => !t.IsGenericParameter).Select(t => new TypeRef(resolver, t, shallow: true)).ToImmutableArray()
: ImmutableArray<TypeRef>.Empty;

if (!shallow)
Expand Down
35 changes: 34 additions & 1 deletion src/Microsoft.VisualStudio.Composition/ReflectionHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -645,7 +645,40 @@ internal static Type GetContractTypeForDelegate(MethodInfo method)

if (method is ConstructorInfo)
{
return closedGeneric.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, Type.DefaultBinder, parameterTypes, Array.Empty<ParameterModifier>());
// Fast path: if none of the parameter types contain generic parameters, the existing lookup works.
if (!parameterTypes.Any(t => t.ContainsGenericParameters))
{
return closedGeneric.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, Type.DefaultBinder, parameterTypes, Array.Empty<ParameterModifier>());
}

// Slow path: the open generic constructor has parameters whose types contain generic type
// parameters (e.g. IFoo<TOptions> on OptionsManager<TOptions>). Type.GetConstructor cannot
// match those against the closed form (IFoo<MyOptions>), so locate the constructor by
// matching the open-generic declaring type's constructor list positionally using metadata token.
var openGenericType = method.DeclaringType!.IsGenericType && !method.DeclaringType.IsGenericTypeDefinition
? method.DeclaringType.GetGenericTypeDefinition().GetTypeInfo()
: method.DeclaringType.GetTypeInfo();
var openCtors = openGenericType.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
int ctorIndex = -1;
for (int i = 0; i < openCtors.Length; i++)
{
if (openCtors[i].MetadataToken == method.MetadataToken)
{
ctorIndex = i;
break;
}
}

if (ctorIndex >= 0)
{
var closedCtors = closedGeneric.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (ctorIndex < closedCtors.Length)
{
return closedCtors[ctorIndex];
}
}

return null;
}
else if (method is MethodInfo)
{
Expand Down
Loading