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
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.TypeSpec.Generator.EmitterRpc;
Expand Down Expand Up @@ -154,9 +155,16 @@ public virtual void AddSharedSourceDirectory(string sharedSourceDirectory)
_sharedSourceDirectories.Add(sharedSourceDirectory);
}

internal HashSet<string> AdditionalRootTypes { get; } = [];
private readonly HashSet<string> _additionalRootTypeNames = [];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

do we really need to create these additional hashsets? Is there possibly another way we can do this using just the original sets?

private readonly HashSet<string> _nonRootTypeNames = [];
private readonly HashSet<TypeProvider> _additionalRootTypeProviders = [];
private readonly HashSet<TypeProvider> _nonRootTypeProviders = [];

internal HashSet<string> NonRootTypes { get; } = [];
internal HashSet<string> AdditionalRootTypes =>
[.. _additionalRootTypeNames, .. _additionalRootTypeProviders.Select(t => t.Type.FullyQualifiedName)];

internal HashSet<string> NonRootTypes =>
[.. _nonRootTypeNames, .. _nonRootTypeProviders.Select(t => t.Type.FullyQualifiedName)];

/// <summary>
/// Adds a type to the list of types to keep.
Expand All @@ -168,21 +176,33 @@ public void AddTypeToKeep(string typeName, bool isRoot = true)
{
if (isRoot)
{
AdditionalRootTypes.Add(typeName);
_additionalRootTypeNames.Add(typeName);
}
else
{
NonRootTypes.Add(typeName);
_nonRootTypeNames.Add(typeName);
}
}

/// <summary>
/// Adds a type to the list of types to keep.
/// Adds a type to the list of types to keep. The type's fully qualified name is resolved lazily
/// when <see cref="AdditionalRootTypes"/> or <see cref="NonRootTypes"/> is accessed, ensuring
/// that any namespace changes made by visitors are reflected.
/// </summary>
/// <param name="type">The type provider representing the type.</param>
/// <param name="isRoot">Whether to treat the type as a root type. Any dependencies of root types will
/// not have their accessibility changed regardless of the 'unreferenced-types-handling' value.</param>
public void AddTypeToKeep(TypeProvider type, bool isRoot = true) => AddTypeToKeep(type.Type.FullyQualifiedName, isRoot);
public void AddTypeToKeep(TypeProvider type, bool isRoot = true)
{
if (isRoot)
{
_additionalRootTypeProviders.Add(type);
}
else
{
_nonRootTypeProviders.Add(type);
}
}

/// <summary>
/// Writes additional output files (e.g. configuration schemas) after the main code generation is complete.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1221,6 +1221,37 @@ public void InternalModelsAreNotIncludedInAdditionalRootTypes()
Assert.IsFalse(rootTypes.Contains("Sample.Models.MockInputModel"));
}

[Test]
public void PublicModelTypeToKeepUpdatesAfterNamespaceChange()
{
var inputModel = InputFactory.Model(
"MockInputModel",
access: "public");

MockHelpers.LoadMockGenerator(
inputModelTypes: [inputModel]);

var modelProvider = CodeModelGenerator.Instance.OutputLibrary.TypeProviders
.SingleOrDefault(t => t.Name == "MockInputModel") as ModelProvider;
Assert.IsNotNull(modelProvider);

var originalFqn = modelProvider!.Type.FullyQualifiedName;
Assert.AreEqual("Sample.Models.MockInputModel", originalFqn);

// Simulate a visitor changing the namespace (e.g., NamespaceVisitor)
modelProvider.Update(@namespace: "NewNamespace.Models");
var newFqn = modelProvider.Type.FullyQualifiedName;
Assert.AreEqual("NewNamespace.Models.MockInputModel", newFqn);

// After namespace change, AdditionalRootTypes should contain the updated FQN
var rootTypes = CodeModelGenerator.Instance.AdditionalRootTypes;
Assert.IsTrue(rootTypes.Contains(newFqn),
$"AdditionalRootTypes should contain the post-visitor FQN '{newFqn}' "
+ $"but only contains: [{string.Join(", ", rootTypes)}]");
Assert.IsFalse(rootTypes.Contains(originalFqn),
$"AdditionalRootTypes should not contain the stale pre-visitor FQN '{originalFqn}'");
}

[TestCase(true, true, InputModelTypeUsage.Output, true, false)]
[TestCase(true, false, InputModelTypeUsage.Output, true, false)]
[TestCase(false, true, InputModelTypeUsage.Output, true, false)]
Expand Down
Loading