Skip to content

Commit

Permalink
wip to refactor methodprovidercollection (#3686)
Browse files Browse the repository at this point in the history
contributes to #3213
  • Loading branch information
m-nash committed Jun 27, 2024
1 parent 7e68cdc commit 08dc5fd
Show file tree
Hide file tree
Showing 13 changed files with 532 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
using Microsoft.Generator.CSharp.ClientModel.Providers;
using Microsoft.Generator.CSharp.Input;
using Microsoft.Generator.CSharp.Providers;

Expand All @@ -18,36 +19,7 @@ public class ScmTypeFactory : TypeFactory
/// </summary>
/// <param name="operation">The input operation to create methods for.</param>
/// <param name="enclosingType">The enclosing type of the operation.</param>
public override MethodProviderCollection? CreateMethodProviders(InputOperation operation, TypeProvider enclosingType)
{
if (_operations.TryGetValue(operation, out var methods))
{
return methods;
}

methods = GetOperationKind(operation).ToString() switch
{
"Default" => MethodProviderCollection.DefaultCSharpMethodCollection(operation, enclosingType),
_ => null,
};

_operations.Add(operation, methods);
return methods;
}

/// <summary>
/// Returns the <see cref="InputOperationKinds"/> of the given operation.
/// By default, the operation kind is <see cref="InputOperationKinds.Default"/>.
/// </summary>
private static InputOperationKinds GetOperationKind(InputOperation operation)
{
return operation switch
{
{ LongRunning: { } } => InputOperationKinds.LongRunning,
{ Paging: { } } => InputOperationKinds.Paging,
_ => InputOperationKinds.Default,
};
}
public override MethodProviderCollection CreateMethodProviders(InputOperation operation, TypeProvider enclosingType) => new ScmMethodProviderCollection(operation, enclosingType);

public virtual CSharpType MatchConditionsType()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Collections.Generic;
using Microsoft.Generator.CSharp.Expressions;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Generator.CSharp.Input;
using Microsoft.Generator.CSharp.Providers;
using Microsoft.Generator.CSharp.Statements;
using static Microsoft.Generator.CSharp.Snippets.Snippet;
using Microsoft.Generator.CSharp.Snippets;

namespace Microsoft.Generator.CSharp.ClientModel.Providers
{
internal class ScmMethodProviderCollection : MethodProviderCollection
{
public ScmMethodProviderCollection(InputOperation operation, TypeProvider enclosingType)
: base(operation, enclosingType)
{
}

protected override IReadOnlyList<MethodProvider> BuildMethods()
{
return
[
// TO-DO: Add Protocol and Convenience methods https://github.com/Azure/autorest.csharp/issues/4585, https://github.com/Azure/autorest.csharp/issues/4586
BuildCreateMessageMethod(_operation, _enclosingType),
BuildProtocolMethod(_operation, _enclosingType, false),
BuildProtocolMethod(_operation, _enclosingType, true)
];
}
private static MethodProvider BuildProtocolMethod(InputOperation operation, TypeProvider enclosingType, bool isAsync)
{
List<ParameterProvider> methodParameters = new();
foreach (InputParameter inputParam in operation.Parameters)
{
if (inputParam.Kind != InputOperationParameterKind.Method)
continue;
methodParameters.Add(ClientModelPlugin.Instance.TypeFactory.CreateCSharpParam(inputParam));
}

var methodModifier = MethodSignatureModifiers.Public | MethodSignatureModifiers.Virtual;
if (isAsync)
{
methodModifier |= MethodSignatureModifiers.Async;
}
var opName = operation.Name.ToCleanName();
var methodSignature = new MethodSignature(
isAsync ? opName + "Async" : opName,
FormattableStringHelpers.FromString(operation.Description),
methodModifier,
GetResponseType(operation.Responses, isAsync),
null,
Parameters: [.. methodParameters, KnownParameters.CancellationTokenParameter]);
MethodBodyStatement[] methodBody =
[
//UsingDeclare("message", typeof(RequestMess)
//using PipelineMessage message = CreateSayHiRequest(headParameter, queryParameter, optionalQuery, options);
isAsync ? new InvokeStaticPropertyExpression(typeof(Task), nameof(Task.CompletedTask), true).Terminate() : EmptyStatement
];

return new MethodProvider(methodSignature, methodBody, enclosingType);
}

private static CSharpType? GetResponseType(IReadOnlyList<OperationResponse> responses, bool isAsync)
{
var response = responses.FirstOrDefault(r => !r.IsErrorResponse);
if (response is null || response.BodyType is null)
return null;
var returnType = ClientModelPlugin.Instance.TypeFactory.CreateCSharpType(response.BodyType);
if (isAsync)
{
returnType = returnType.WrapInTask();
}
return returnType;
}

private static MethodProvider BuildCreateMessageMethod(InputOperation operation, TypeProvider enclosingType)
{
// TO-DO: properly build method https://github.com/Azure/autorest.csharp/issues/4583
List<ParameterProvider> methodParameters = new();
foreach (var inputParam in operation.Parameters)
{
methodParameters.Add(ClientModelPlugin.Instance.TypeFactory.CreateCSharpParam(inputParam));
}

var methodModifier = MethodSignatureModifiers.Internal;
var methodSignatureName = $"Create{operation.Name.ToCleanName()}Request";
var methodSignature = new MethodSignature(methodSignatureName, FormattableStringHelpers.FromString(operation.Description), methodModifier, null, null, Parameters: methodParameters);
var methodBody = Throw(New.NotImplementedException(Literal("Method not implemented.")));

return new MethodProvider(methodSignature, methodBody, enclosingType);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Microsoft.Generator.CSharp.ClientModel.Tests
{
internal class MockTypeFactory : ScmTypeFactory
{
public override MethodProviderCollection? CreateMethodProviders(InputOperation operation, TypeProvider enclosingType)
public override MethodProviderCollection CreateMethodProviders(InputOperation operation, TypeProvider enclosingType)
{
throw new NotImplementedException();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using Microsoft.Generator.CSharp.Providers;

namespace Microsoft.Generator.CSharp.ClientModel.Tests
{
internal class MockTypeProvider : TypeProvider
{
public override string RelativeFilePath => throw new NotImplementedException();
public override string Name => throw new NotImplementedException();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,19 @@
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using Microsoft.Generator.CSharp.ClientModel.Providers;
using Microsoft.Generator.CSharp.Input;
using Microsoft.Generator.CSharp.Providers;
using Moq;
using Moq.Protected;
using NUnit.Framework;

namespace Microsoft.Generator.CSharp.Tests.Providers
namespace Microsoft.Generator.CSharp.ClientModel.Tests.Providers
{
internal class MethodProviderCollectionTests
internal class ScmMethodProviderCollectionTests
{
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
private TypeFactory _typeFactory;
private ScmTypeFactory _typeFactory;
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
private readonly string _mocksFolder = "Mocks";
private FieldInfo? _mockPlugin;
Expand All @@ -25,16 +26,17 @@ internal class MethodProviderCollectionTests
public void Setup()
{
var mockParameter = new ParameterProvider("mockParam", $"mock description", typeof(bool), null);
var mockTypeFactory = new Mock<TypeFactory>() { };
var mockTypeFactory = new Mock<ScmTypeFactory>() { };
mockTypeFactory.Protected().Setup<CSharpType>("CreateCSharpTypeCore", ItExpr.IsAny<InputType>()).Returns(new CSharpType(typeof(bool)));
mockTypeFactory.Setup(t => t.CreateCSharpParam(It.IsAny<InputParameter>())).Returns(mockParameter);
_typeFactory = mockTypeFactory.Object;

var configFilePath = Path.Combine(AppContext.BaseDirectory, _mocksFolder);
// initialize the mock singleton instance of the plugin
_mockPlugin = typeof(CodeModelPlugin).GetField("_instance", BindingFlags.Static | BindingFlags.NonPublic);
var mockGeneratorContext = new Mock<GeneratorContext>(Configuration.Load(configFilePath));
var mockPluginInstance = new Mock<CodeModelPlugin>(mockGeneratorContext.Object) { };
_mockPlugin = typeof(ClientModelPlugin).GetField("_instance", BindingFlags.Static | BindingFlags.NonPublic);
var mockConfiguration = new Mock<Configuration>() { };
var mockGeneratorContext = new Mock<GeneratorContext>(mockConfiguration.Object);
var mockPluginInstance = new Mock<ClientModelPlugin>(mockGeneratorContext.Object) { };
mockPluginInstance.SetupGet(p => p.TypeFactory).Returns(_typeFactory);

_mockPlugin?.SetValue(null, mockPluginInstance.Object);
Expand All @@ -50,9 +52,9 @@ public void Teardown()
[TestCaseSource(nameof(DefaultCSharpMethodCollectionTestCases))]
public void TestDefaultCSharpMethodCollection(InputOperation inputOperation)
{
var methodCollection = MethodProviderCollection.DefaultCSharpMethodCollection(inputOperation, new MockTypeProvider());
var methodCollection = new ScmMethodProviderCollection(inputOperation, new MockTypeProvider());
Assert.IsNotNull(methodCollection);
Assert.AreEqual(1, methodCollection?.Count);
Assert.AreEqual(3, methodCollection.Count);

var method = methodCollection![0];
var signature = method.Signature;
Expand Down Expand Up @@ -93,5 +95,4 @@ public static IEnumerable<TestCaseData> DefaultCSharpMethodCollectionTestCases
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ public class Configuration
{
private const string ConfigurationFileName = "Configuration.json";

// for mocking
protected Configuration()
{
OutputDirectory = null!;
AdditionalConfigOptions = null!;
LibraryName = null!;
Namespace = null!;
}

private Configuration(
string outputPath,
Dictionary<string, BinaryData> additionalConfigOptions,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Microsoft.Generator.CSharp.Statements;

namespace Microsoft.Generator.CSharp.Expressions
{
public sealed record InvokeStaticPropertyExpression(CSharpType? ContainingType, string PropertyName, bool CallAsAsync = false) : ValueExpression
{
internal override void Write(CodeWriter writer)
{
writer.AppendRawIf("await ", CallAsAsync);
if (ContainingType != null)
{
writer.Append($"{ContainingType}.");
}

writer.AppendRaw(PropertyName);
writer.AppendRawIf(".ConfigureAwait(false)", CallAsAsync);
}

private MethodBodyStatement? _terminated;
public MethodBodyStatement Terminate() => _terminated ??= new ExpressionStatement(this);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Generator.CSharp.Providers;

namespace Microsoft.Generator.CSharp
Expand Down Expand Up @@ -608,5 +609,15 @@ public CSharpType MakeGenericType(IReadOnlyList<CSharpType> arguments)
return new CSharpType(Implementation, arguments, IsNullable);
}
}

public CSharpType WrapInTask()
{
if (IsFrameworkType && FrameworkType == typeof(Task<>))
{
return this;
}

return new CSharpType(typeof(Task<>), this);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ public CSharpType CreateCSharpType(InputType inputType)
/// <returns>An instance of <see cref="MethodProviderCollection"/> containing the chain of methods
/// associated with the input operation, or <c>null</c> if no methods are constructed.
/// </returns>
public abstract MethodProviderCollection? CreateMethodProviders(InputOperation operation, TypeProvider enclosingType);
public virtual MethodProviderCollection CreateMethodProviders(InputOperation operation, TypeProvider enclosingType) => new(operation, enclosingType);

/// <summary>
/// Factory method for retrieving the serialization format for a given input type.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,68 +3,41 @@

using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Generator.CSharp.Input;
using Microsoft.Generator.CSharp.Snippets;

namespace Microsoft.Generator.CSharp.Providers
{
/// <summary>
/// Represents an immutable collection of methods that are associated with an operation <see cref="InputOperation"/>.
/// </summary>
public sealed class MethodProviderCollection : IReadOnlyList<MethodProvider>
public class MethodProviderCollection : IReadOnlyList<MethodProvider>
{
private readonly IReadOnlyList<MethodProvider> _cSharpMethods;
private IReadOnlyList<MethodProvider>? _cSharpMethods;
protected readonly InputOperation _operation;
protected readonly TypeProvider _enclosingType;

private MethodProviderCollection(IEnumerable<MethodProvider>? methods)
public MethodProviderCollection(InputOperation operation, TypeProvider enclosingType)
{
_cSharpMethods = methods?.ToList() ?? [];
_operation = operation;
_enclosingType = enclosingType;
}

/// <summary>
/// Builds a default <see cref="MethodProviderCollection"/> for the given <see cref="InputOperation"/>
/// with a single method that creates a message.
/// </summary>
/// <param name="operation">The <see cref="InputOperation"/> to convert.</param>
/// <param name="enclosingType">The <see cref="TypeProvider"/> that will contain the methods.</param>
public static MethodProviderCollection DefaultCSharpMethodCollection(InputOperation operation, TypeProvider enclosingType)
{
var createMessageMethod = BuildCreateMessageMethod(operation, enclosingType);
var cSharpMethods = new List<MethodProvider>() { createMessageMethod };
// TO-DO: Add Protocol and Convenience methods https://github.com/Azure/autorest.csharp/issues/4585, https://github.com/Azure/autorest.csharp/issues/4586
return new MethodProviderCollection(cSharpMethods);
}
protected virtual IReadOnlyList<MethodProvider> BuildMethods() => [];
public IReadOnlyList<MethodProvider> MethodProviders => _cSharpMethods ??= BuildMethods();

public MethodProvider this[int index]
{
get { return _cSharpMethods[index]; }
get { return MethodProviders[index]; }
}

public int Count
{
get { return _cSharpMethods.Count; }
}

private static MethodProvider BuildCreateMessageMethod(InputOperation operation, TypeProvider enclosingType)
{
// TO-DO: properly build method https://github.com/Azure/autorest.csharp/issues/4583
List<ParameterProvider> methodParameters = new();
foreach (var inputParam in operation.Parameters)
{
methodParameters.Add(CodeModelPlugin.Instance.TypeFactory.CreateCSharpParam(inputParam));
}

var methodModifier = MethodSignatureModifiers.Internal;
var methodSignatureName = $"Create{operation.Name.ToCleanName()}Request";
var methodSignature = new MethodSignature(methodSignatureName, FormattableStringHelpers.FromString(operation.Description), methodModifier, null, null, Parameters: methodParameters);
var methodBody = Snippet.Throw(Snippet.New.NotImplementedException(Snippet.Literal("Method not implemented.")));

return new MethodProvider(methodSignature, methodBody, enclosingType);
get { return MethodProviders.Count; }
}

public IEnumerator<MethodProvider> GetEnumerator()
{
return _cSharpMethods.GetEnumerator();
return MethodProviders.GetEnumerator();
}

IEnumerator IEnumerable.GetEnumerator()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Microsoft.Generator.CSharp.Tests
{
internal class MockTypeFactory : TypeFactory
{
public override MethodProviderCollection? CreateMethodProviders(InputOperation operation, TypeProvider enclosingType)
public override MethodProviderCollection CreateMethodProviders(InputOperation operation, TypeProvider enclosingType)
{
throw new NotImplementedException();
}
Expand Down
Loading

0 comments on commit 08dc5fd

Please sign in to comment.