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 @@ -123,7 +123,7 @@ public MrwSerializationTypeDefinition(InputModelType inputModel, ModelProvider m
protected override TypeSignatureModifiers BuildDeclarationModifiers() => _model.DeclarationModifiers;

protected override IReadOnlyList<MethodProvider> BuildMethodsForBackCompatibility(IEnumerable<MethodProvider> originalMethods)
=> [.. originalMethods];
=> base.BuildMethodsForBackCompatibility(originalMethods);

protected override IReadOnlyList<ConstructorProvider> BuildConstructorsForBackCompatibility(IEnumerable<ConstructorProvider> originalConstructors)
=> [.. originalConstructors];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.TypeSpec.Generator.ClientModel.Providers;
using Microsoft.TypeSpec.Generator.Expressions;
using Microsoft.TypeSpec.Generator.Input;
using Microsoft.TypeSpec.Generator.Input.Extensions;
using Microsoft.TypeSpec.Generator.Primitives;
using Microsoft.TypeSpec.Generator.Providers;
using Microsoft.TypeSpec.Generator.Snippets;
using Microsoft.TypeSpec.Generator.SourceInput;
using Microsoft.TypeSpec.Generator.Statements;
using Microsoft.TypeSpec.Generator.Tests.Common;
using NUnit.Framework;
Expand Down Expand Up @@ -804,6 +806,49 @@ public void TestBuildExplicitFromClientResult()
Assert.IsNotNull(methodBody);
}

[Test]
public async Task ExplicitClientResultOperatorPreservesLastContractParameterName()
{
var inputModel = InputFactory.Model("mockInputModel");
var generator = MockHelpers.LoadMockGenerator(
inputModels: () => [inputModel],
clientResponseApi: new RenamedClientResultProvider(ValueExpression.Empty));
generator.Object.SourceInputModel = new SourceInputModel(
null,
await Helpers.GetCompilationFromDirectoryAsync());
generator.Object.TypeFactory.RootOutputModels.Add(inputModel);
var model = generator.Object.OutputLibrary.TypeProviders.OfType<ModelProvider>().Single();

model.EnsureBuilt();
CSharpGen.ProcessTypeProvidersForBackCompatibility(generator.Object.OutputLibrary);

var writer = new TypeProviderWriter(new FilteredMethodsTypeProvider(
model.SerializationProviders.Single(),
name => name == model.Name));

Assert.AreEqual(Helpers.GetExpectedFromFile(), writer.Write().Content);
}

[Test]
public void ExplicitClientResultOperatorKeepsCurrentParameterNameWithoutLastContract()
{
var inputModel = InputFactory.Model("mockInputModel");
var generator = MockHelpers.LoadMockGenerator(
inputModels: () => [inputModel],
clientResponseApi: new RenamedClientResultProvider(ValueExpression.Empty));
generator.Object.TypeFactory.RootOutputModels.Add(inputModel);
var model = generator.Object.OutputLibrary.TypeProviders.OfType<ModelProvider>().Single();

model.EnsureBuilt();
CSharpGen.ProcessTypeProvidersForBackCompatibility(generator.Object.OutputLibrary);

var writer = new TypeProviderWriter(new FilteredMethodsTypeProvider(
model.SerializationProviders.Single(),
name => name == model.Name));

Assert.AreEqual(Helpers.GetExpectedFromFile(), writer.Write().Content);
}

[Test]
public void TestExplicitFromClientResultNotGeneratedForNonRootOutputModel()
{
Expand All @@ -818,6 +863,12 @@ public void TestExplicitFromClientResultNotGeneratedForNonRootOutputModel()
Assert.IsNull(method);
}

private record RenamedClientResultProvider(ValueExpression Original)
: ClientResultProvider(Original)
{
public override string ResponseParameterName => "response";
}

[TestCase(true)]
[TestCase(false)]
public void TestReadOnlyPropertiesHaveWireCheck(bool isRequired)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// <auto-generated/>

#nullable disable

using System.ClientModel;
using System.ClientModel.Primitives;
using System.Text.Json;
using Sample.Models;

namespace Sample
{
public partial class MockInputModel
{
public static explicit operator MockInputModel(global::System.ClientModel.ClientResult response)
Comment thread
jorgerangel-msft marked this conversation as resolved.
{
global::System.ClientModel.Primitives.PipelineResponse response0 = response.GetRawResponse();
using global::System.Text.Json.JsonDocument document = global::System.Text.Json.JsonDocument.Parse(response0.Content, global::Sample.ModelSerializationExtensions.JsonDocumentOptions);
return global::Sample.Models.MockInputModel.DeserializeMockInputModel(document.RootElement, global::Sample.ModelSerializationExtensions.WireOptions);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// <auto-generated/>

#nullable disable

using System.ClientModel;
using System.ClientModel.Primitives;
using System.Text.Json;
using Sample.Models;

namespace Sample
{
public partial class MockInputModel
{
public static explicit operator MockInputModel(global::System.ClientModel.ClientResult result)
{
global::System.ClientModel.Primitives.PipelineResponse response = result.GetRawResponse();
using global::System.Text.Json.JsonDocument document = global::System.Text.Json.JsonDocument.Parse(response.Content, global::Sample.ModelSerializationExtensions.JsonDocumentOptions);
return global::Sample.Models.MockInputModel.DeserializeMockInputModel(document.RootElement, global::Sample.ModelSerializationExtensions.WireOptions);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.ClientModel;

namespace Sample.Models
{
public partial class MockInputModel
{
public static explicit operator MockInputModel(ClientResult result) => throw null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,8 @@ await GeneratedCodeWorkspace.LoadBaselineContract(),

LoggingHelpers.LogElapsedTime("All visitors have been applied");

foreach (var outputType in output.TypeProviders)
{
// Ensure back-compatibility processing is done after all visitors have run
outputType.ProcessTypeForBackCompatibility();
}
// Ensure back-compatibility processing is done after all visitors have run
ProcessTypeProvidersForBackCompatibility(output);

try
{
Expand Down Expand Up @@ -211,6 +208,35 @@ private static void FilterCustomizedMembers(TypeProvider typeProvider)
typeProvider.Fields);
}

internal static void ProcessTypeProvidersForBackCompatibility(OutputLibrary output)
{
var visited = new HashSet<TypeProvider>();
foreach (var typeProvider in output.TypeProviders)
{
ProcessTypeForBackCompatibility(typeProvider, visited);
}
}

private static void ProcessTypeForBackCompatibility(TypeProvider typeProvider, HashSet<TypeProvider> visited)
{
if (!visited.Add(typeProvider))
{
return;
}

typeProvider.ProcessTypeForBackCompatibility();

foreach (var serializationProvider in typeProvider.SerializationProviders)
{
ProcessTypeForBackCompatibility(serializationProvider, visited);
}

foreach (var nestedType in typeProvider.NestedTypes)
{
ProcessTypeForBackCompatibility(nestedType, visited);
}
}

/// <summary>
/// Clears the output directory specified by <paramref name="path"/>. If <paramref name="filesToKeep"/> is not null,
/// the specified files in the output directory will not be deleted.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// <auto-generated/>

#nullable disable

namespace Test
{
public partial class SerializationImplicitBackCompatType
{
public static implicit operator string(global::Test.SerializationImplicitBackCompatType published)
{
return null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Test
{
public partial class SerializationImplicitBackCompatType
{
public static implicit operator string(SerializationImplicitBackCompatType published) => null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// <auto-generated/>

#nullable disable

namespace Test
{
public partial class SerializationBackCompatType
{
public static explicit operator SerializationBackCompatType(string result)
{
return null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Test
{
public partial class SerializationBackCompatType
{
public static explicit operator SerializationBackCompatType(string result) => null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Microsoft.TypeSpec.Generator.Snippets;
using Microsoft.TypeSpec.Generator.Statements;
using Microsoft.TypeSpec.Generator.Tests.Common;
using Microsoft.TypeSpec.Generator.Tests.TestHelpers;
using Microsoft.TypeSpec.Generator.Utilities;
using NUnit.Framework;

Expand Down Expand Up @@ -968,6 +969,64 @@ public async Task BuildMethodsForBackCompatibilityKeepsUnpublishedParameterName(
Assert.AreEqual(Helpers.GetExpectedFromFile(), actual);
}

[Test]
public async Task BuildMethodsForBackCompatibilityRestoresOperatorParameterNameOnSerializationProvider()
{
await MockHelpers.LoadMockGeneratorAsync(lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync());

var owner = new TestTypeProvider(name: "SerializationBackCompatType", ns: "Test");
var serializationProvider = new TestTypeProvider(name: "SerializationBackCompatType", ns: "Test");
var response = new ParameterProvider("response", $"", new CSharpType(typeof(string)));
var explicitOperator = new MethodProvider(
new MethodSignature(
serializationProvider.Name,
$"",
MethodSignatureModifiers.Public | MethodSignatureModifiers.Static | MethodSignatureModifiers.Explicit | MethodSignatureModifiers.Operator,
serializationProvider.Type,
$"",
[response]),
Snippet.Return(Snippet.Null),
serializationProvider);

serializationProvider.Update(methods: [explicitOperator]);
owner.Update(serializations: [serializationProvider]);

var outputLibrary = new TestOutputLibrary(owner);
CSharpGen.ProcessTypeProvidersForBackCompatibility(outputLibrary);

var actual = new TypeProviderWriter(serializationProvider).Write().Content;
Assert.AreEqual(Helpers.GetExpectedFromFile(), actual);
}

[Test]
public async Task BuildMethodsForBackCompatibilityRestoresImplicitOperatorParameterNameOnSerializationProvider()
{
await MockHelpers.LoadMockGeneratorAsync(lastContractCompilation: async () => await Helpers.GetCompilationFromDirectoryAsync());

var owner = new TestTypeProvider(name: "SerializationImplicitBackCompatType", ns: "Test");
var serializationProvider = new TestTypeProvider(name: "SerializationImplicitBackCompatType", ns: "Test");
var current = new ParameterProvider("current", $"", serializationProvider.Type);
var implicitOperator = new MethodProvider(
new MethodSignature(
"BinaryContent",
$"",
MethodSignatureModifiers.Public | MethodSignatureModifiers.Static | MethodSignatureModifiers.Implicit | MethodSignatureModifiers.Operator,
new CSharpType(typeof(string)),
$"",
[current]),
Snippet.Return(Snippet.Null),
serializationProvider);

serializationProvider.Update(methods: [implicitOperator]);
owner.Update(serializations: [serializationProvider]);

var outputLibrary = new TestOutputLibrary(owner);
CSharpGen.ProcessTypeProvidersForBackCompatibility(outputLibrary);

var actual = new TypeProviderWriter(serializationProvider).Write().Content;
Assert.AreEqual(Helpers.GetExpectedFromFile(), actual);
}

// Validates the positional fallback: when a spec parameter's previously-published name matches
// neither its current name nor its spec original name (e.g. a rename by a different generator),
// it is restored from the last-contract method that matches by signature (name and parameter types).
Expand Down
Loading