Skip to content

Support generic imports. - #717

Open
Tobias Käs (weltkante) wants to merge 9 commits into
microsoft:mainfrom
weltkante:work
Open

Support generic imports.#717
Tobias Käs (weltkante) wants to merge 9 commits into
microsoft:mainfrom
weltkante:work

Conversation

@weltkante

Copy link
Copy Markdown

Fixes #457

As mentioned, mostly AI generated. The approach makes sense to me. I left the original generated comments intact during cleanup since they might help understanding during review, if further cleanup or other refactoring is desired let me know and I'll work on that. If the approach isn't desireable thats fine too, I just wanted to get this is out of my backlog ;)

@weltkante

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

@AArnott Andrew Arnott (AArnott) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for sharing this. I think it's shaping up.

Comment thread test/Microsoft.VisualStudio.Composition.Tests/GenericImportTests.cs
Comment thread src/Microsoft.VisualStudio.Composition/ReflectionHelpers.cs Outdated
Comment thread docfx/docs/mef_library_differences.md Outdated
@AArnott

Andrew Arnott (AArnott) commented Jul 21, 2026

Copy link
Copy Markdown
Member

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

@weltkante

Copy link
Copy Markdown
Author

Ah, thats a test failure that slipped through my local tests, due to the way of it being reported. I'll have a look, seems to be a genuine missed case.

@weltkante

Copy link
Copy Markdown
Author

ImportMany attributes left some generic parameters which caused issues roundtripping through a serialized container. I've reverted the logic in the idividual AttributedPartDiscovery branches and instead have it now directly in GetExportTypeIdentityConstraints and GetImportMetadataForGenericTypeImport - looks cleaner too. Also added a test specifically covering this scenario.

@weltkante
Tobias Käs (weltkante) marked this pull request as ready for review August 22, 2026 12:48
Copilot AI lite review requested due to automatic review settings August 22, 2026 12:48
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds support for “parameterized generic imports” where an open-generic part imports a constructed generic type whose type arguments are the part’s own generic parameters (e.g. OptionsManager<TOptions> importing IOptionsFactory<TOptions>), enabling common Generic Host / Options DI patterns (issue #457) to compose in VS MEF.

Changes:

  • Introduces GenericParameterIndexes import metadata and runtime substitution to close parameterized generic imports using the importing part’s concrete type arguments.
  • Updates runtime import handling to compute effective closed import-site types for direct, Lazy<T>, ExportFactory<T>, and ImportMany collection forms.
  • Adds/extends tests (including cache/reload) and documentation describing supported generic import scenarios.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
test/Microsoft.VisualStudio.Composition.Tests/Reflection/TypeRefTests.cs Loosens an assertion on TypeLoadException message contents.
test/Microsoft.VisualStudio.Composition.Tests/GenericImportTests.cs Adds coverage for parameterized generic imports across direct/Lazy/ExportFactory/ImportMany shapes.
test/Microsoft.VisualStudio.Composition.Tests/CacheAndReloadTests.cs Verifies parameterized generic imports survive cache save/load.
src/Microsoft.VisualStudio.Composition/RuntimeExportProviderFactory+RuntimeExportProvider.cs Computes effective closed import-site types/metadata at runtime for parameterized generic imports.
src/Microsoft.VisualStudio.Composition/ReflectionHelpers.cs Adjusts open→closed generic constructor mapping for importing constructors with generic-parameter-containing parameter types.
src/Microsoft.VisualStudio.Composition/Reflection/TypeRef.cs Avoids constructing TypeRef generic arguments for generic parameters.
src/Microsoft.VisualStudio.Composition/PartDiscovery.cs Emits GenericParameterIndexes metadata and avoids type-identity constraints for parameterized generic import contracts.
src/Microsoft.VisualStudio.Composition/CompositionConstants.cs Adds the GenericParameterIndexes metadata name constant.
src/Microsoft.VisualStudio.Composition/ComposedPart.cs Exempts parameterized generic imports from the “generic type parameters not supported” validation diagnostic.
docfx/docs/mef_library_differences.md Documents supported generic import patterns and explicitly calls out unsupported “bare type parameter” imports.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +674 to +681
if (ctorIndex >= 0)
{
var closedCtors = closedGeneric.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (ctorIndex < closedCtors.Length)
{
return closedCtors[ctorIndex];
}
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting change, can't tell which is better, as far as I'm concerned both sound like implementation details that in practice always hold. But the direct token matching for sure is simpler and allows to get rid of the dual loop, so I'd adopt it if there is no reason against it.

Copilot AI review requested due to automatic review settings August 22, 2026 18:20

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

Suppressed comments (2)

Previously missed (1) — in code that hasn't changed since the last review.

src/Microsoft.VisualStudio.Composition/RuntimeExportProviderFactory+RuntimeExportProvider.cs:157

  • This wrapper reconstruction assumes Lazy/ExportFactory have exactly one generic argument. VS MEF also supports Lazy<T, TMetadata> and ExportFactory<T, TMetadata> (see MetadataType logic), so MakeGenericType(effectiveElementType) will throw for those cases. Preserve any existing metadata type argument when rebuilding the wrapper.

This issue also appears on line 168 of the same file.

                                // ImportingSiteTypeWithoutCollection is the wrapper type: Lazy<IFoo<TOptions>> or ExportFactory<IFoo<TOptions>>
                                // Reconstruct it as Lazy<IFoo<MyOptions>> or ExportFactory<IFoo<MyOptions>>
                                effectiveImportSiteWithoutCollection = import.ImportingSiteTypeWithoutCollection
                                    .GetGenericTypeDefinition()
                                    .MakeGenericType(effectiveElementType);

src/Microsoft.VisualStudio.Composition/RuntimeExportProviderFactory+RuntimeExportProvider.cs:172

  • When computing effectiveImportSiteType for parameterized generic imports, the code uses effectiveElementType (the unwrapped contract type). For ImportMany, the collection element type is ImportingSiteTypeWithoutCollection, which may be Lazy/ExportFactory-wrapped; using the unwrapped type can make createability/assignment checks incorrect for collection types like ICollection<Lazy<T, TMetadata>> and custom collections.
                            }
                            else if (outerType.IsGenericType)
                            {
                                effectiveImportSiteType = outerType.GetGenericTypeDefinition().MakeGenericType(effectiveElementType);
                            }

Copilot AI review requested due to automatic review settings August 22, 2026 18:59
@weltkante

Tobias Käs (weltkante) commented Aug 22, 2026

Copy link
Copy Markdown
Author

Hmm, that copilot comment brought up some cases that weren't handled. Added tests for them. Also the documentation changes I suggested earlier apparently didn't apply, so I reapplied them manually.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

src/Microsoft.VisualStudio.Composition/Reflection/TypeRef.cs:118

  • TypeRef now unconditionally filters out generic-parameter type arguments. If a constructed generic type has a mix of generic parameters and concrete type arguments (e.g. Foo<T,int>), this will produce a TypeRef whose GenericTypeArguments count is less than GenericTypeParameterCount. Later, Resolve() will call MakeGenericType with too few arguments and throw an ArgumentException, which is a behavior regression vs the previous early rejection. Consider only applying the filtering when all type arguments are generic parameters (the supported “parameterized generic import” case), and explicitly reject partially-open generic types to avoid creating an invalid TypeRef state.
                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 => !t.IsGenericParameter).Select(t => new TypeRef(resolver, t, shallow: true)).ToImmutableArray()
                    : ImmutableArray<TypeRef>.Empty;

Copilot AI review requested due to automatic review settings August 22, 2026 19:25

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

Comment on lines +446 to +453
if (!importingPartTracker.ImportMetadata.TryGetValue(CompositionConstants.GenericParametersMetadataName, out var outerTypeArgsObj) || outerTypeArgsObj is not Type[] outerTypeArgs)
{
return importMetadata;
}

Type[] closedTypeArgs = indexes.Select(i => outerTypeArgs[i]).ToArray();
return ImmutableDictionary.CreateRange(importMetadata)
.SetItem(CompositionConstants.GenericParametersMetadataName, closedTypeArgs);

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems to be a false positive and not reproducable. Quote:

LazyMetadataWrapper.TryGetValue runs SubstituteValueIfRequired with Direction.ToOriginalValue, so reads through the dictionary always hand back Type[]. The substitution is only observable if you deliberately call LazyMetadataWrapper.TryUnwrap first — which is exactly what GetPartConstructedTypeRef does (to avoid forcing type loads), and why that method needs its two-form handling. GetEffectiveImportMetadata doesn't unwrap, so is not Type[] never trips there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support generic imports (GenericHost/Options DI)

3 participants