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 @@ -538,9 +538,17 @@ protected internal override PropertyProvider[] BuildProperties()
// Targeted backcompat fix for the case where properties were previously generated as read-only collections
if (outputProperty.Type.IsReadWriteList || outputProperty.Type.IsReadWriteDictionary)
{
// We compare Arguments by name (not just ElementType) to cover both list element types
// and dictionary key/value types. This ensures we only override the collection wrapper
// (e.g. IReadOnlyList<T> → IList<T>) and not when the element type itself has changed.
// We use AreNamesEqual rather than Equals because the argument types may come from
// different sources (TypeProvider vs compiled assembly) but represent the same logical type.
if (LastContractPropertiesMap.TryGetValue(outputProperty.Name,
out CSharpType? lastContractPropertyType) &&
!outputProperty.Type.Equals(lastContractPropertyType))
!outputProperty.Type.Equals(lastContractPropertyType) &&
outputProperty.Type.Arguments.Count == lastContractPropertyType.Arguments.Count &&
outputProperty.Type.Arguments.Zip(lastContractPropertyType.Arguments).All(
pair => pair.First.AreNamesEqual(pair.Second)))
{
outputProperty.Type = lastContractPropertyType.ApplyInputSpecProperty(property);
CodeModelGenerator.Instance.Emitter.Info($"Changed property {Name}.{outputProperty.Name} type to {lastContractPropertyType} to match last contract.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1062,6 +1062,51 @@ await MockHelpers.LoadMockGeneratorAsync(
Assert.IsTrue(moreItemsProperty!.Type.Equals(new CSharpType(typeof(IReadOnlyDictionary<,>), typeof(string), elementEnumProvider.Type)));
}

[Test]
public async Task BackCompat_CollectionPropertyTypeNotOverriddenWhenElementTypeChanges()
{
// Simulate the scenario where the element type of a collection property has changed
// (e.g., from Record<unknown>[] to BulkVMConfiguration[] via @typeChangedFrom).
// The last contract has IList<IDictionary<string, BinaryData>> but the new code model
// produces IList<NewElementModel>. The override should NOT apply because the element
// type has changed.
var newElementModel = InputFactory.Model(
"NewElementModel",
properties:
[
InputFactory.Property("name", InputPrimitiveType.String)
]);
var inputModel = InputFactory.Model(
"MockInputModel",
properties:
[
InputFactory.Property("items", InputFactory.Array(newElementModel)),
InputFactory.Property("moreItems", InputFactory.Dictionary(newElementModel))
]);

await MockHelpers.LoadMockGeneratorAsync(
inputModelTypes: [inputModel, newElementModel],
lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync());

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

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

// The items property should use the new element type (IList<NewElementModel>), not be
// overridden to the old type (IList<IDictionary<string, BinaryData>>) from last contract
var itemsProperty = modelProvider!.Properties.FirstOrDefault(p => p.Name == "Items");
Assert.IsNotNull(itemsProperty);
Assert.IsTrue(itemsProperty!.Type.Equals(new CSharpType(typeof(IList<>), newElementModelProvider!.Type)));

// The moreItems property should use the new element type (IDictionary<string, NewElementModel>), not be
// overridden to the old type (IDictionary<string, IDictionary<string, BinaryData>>) from last contract
var moreItemsProperty = modelProvider.Properties.FirstOrDefault(p => p.Name == "MoreItems");
Assert.IsNotNull(moreItemsProperty);
Assert.IsTrue(moreItemsProperty!.Type.Equals(new CSharpType(typeof(IDictionary<,>), typeof(string), newElementModelProvider.Type)));
}

[Test]
public async Task BackCompat_InternalTypesAreIgnored()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Collections.Generic;

namespace Sample.Models
{
public partial class MockInputModel
{
public IList<IDictionary<string, BinaryData>> Items { get; }
public IDictionary<string, IDictionary<string, BinaryData>> MoreItems { get; }
}
}