Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move serialization constructors and raw field #3414

Merged
merged 22 commits into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
3e69b46
feat: add deserialization ctors and raw field
jorgerangel-msft May 15, 2024
a3cec12
Merge branch 'main' of https://github.com/microsoft/typespec into ser…
jorgerangel-msft May 22, 2024
516ccf9
pr feedback
jorgerangel-msft May 22, 2024
6201bcf
Merge branch 'main' of https://github.com/microsoft/typespec into ser…
jorgerangel-msft May 22, 2024
10b5428
additional feedback
jorgerangel-msft May 23, 2024
844708b
Merge branch 'main' of https://github.com/microsoft/typespec into ser…
jorgerangel-msft May 23, 2024
c755358
Merge branch 'main' of https://github.com/microsoft/typespec into ser…
jorgerangel-msft May 23, 2024
9762265
more feedback
jorgerangel-msft May 24, 2024
09427b2
Merge branch 'main' of https://github.com/microsoft/typespec into ser…
jorgerangel-msft May 24, 2024
ee25250
do not add raw field for structs
jorgerangel-msft May 24, 2024
4a7e175
Merge branch 'main' of https://github.com/microsoft/typespec into ser…
jorgerangel-msft May 28, 2024
ea5d5da
Merge branch 'main' of https://github.com/microsoft/typespec into ser…
jorgerangel-msft May 31, 2024
eaf7288
refactor based on latest from main
jorgerangel-msft May 31, 2024
c462b71
Merge branch 'main' of https://github.com/microsoft/typespec into ser…
jorgerangel-msft May 31, 2024
debd96a
Merge branch 'main' of https://github.com/microsoft/typespec into ser…
jorgerangel-msft May 31, 2024
3904d4e
Merge branch 'main' of https://github.com/microsoft/typespec into ser…
jorgerangel-msft Jun 3, 2024
bb4f555
more feedback
jorgerangel-msft Jun 3, 2024
4ac3537
Merge branch 'main' of https://github.com/microsoft/typespec into ser…
jorgerangel-msft Jun 3, 2024
e2b8265
feedback
jorgerangel-msft Jun 3, 2024
1d11e98
Merge branch 'main' of https://github.com/microsoft/typespec into ser…
jorgerangel-msft Jun 3, 2024
5b44f3b
add parameter provider unit tests
jorgerangel-msft Jun 4, 2024
2d386ff
Merge branch 'main' of https://github.com/microsoft/typespec into ser…
jorgerangel-msft Jun 4, 2024
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 @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using Microsoft.Generator.CSharp.Input;
using Microsoft.Generator.CSharp.ClientModel.Snippets;
using Microsoft.Generator.CSharp.Snippets;

Expand All @@ -28,10 +29,11 @@ public class ClientModelPlugin : CodeModelPlugin
/// Returns the serialization type providers for the given model type provider.
/// </summary>
/// <param name="provider">The model type provider.</param>
public override IReadOnlyList<TypeProvider> GetSerializationTypeProviders(ModelTypeProvider provider)
/// <param name="inputModel">The input model.</param>
public override IReadOnlyList<TypeProvider> GetSerializationTypeProviders(ModelTypeProvider provider, InputModelType inputModel)
{
// Add MRW serialization type provider
return [new MrwSerializationTypeProvider(provider)];
return [new MrwSerializationTypeProvider(provider, inputModel)];
}

[ImportingConstructor]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
<ProjectReference Include="..\..\Microsoft.Generator.CSharp\src\Microsoft.Generator.CSharp.csproj" />
</ItemGroup>

<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)..\..\Microsoft.Generator.CSharp\src\Shared\**\*.cs" LinkBase="Shared" />
</ItemGroup>

<!-- Copy output to package dist path for local execution and -->
<Target Name="CopyForNpmPackage" AfterTargets="Build">
<Message Text="Copying output to dist path" Importance="high" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@

using System;
using System.ClientModel.Primitives;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using Microsoft.Generator.CSharp.ClientModel.Snippets;
using Microsoft.Generator.CSharp.Expressions;
using Microsoft.Generator.CSharp.Input;
using Microsoft.Generator.CSharp.Snippets;
using Microsoft.Generator.CSharp.Statements;

namespace Microsoft.Generator.CSharp.ClientModel
{
Expand All @@ -16,26 +19,34 @@ namespace Microsoft.Generator.CSharp.ClientModel
/// </summary>
internal sealed class MrwSerializationTypeProvider : TypeProvider
{
private readonly Parameter SerializationOptionsParameter =
new("options", $"The client options.", typeof(ModelReaderWriterOptions));
private readonly Parameter _serializationOptionsParameter =
new("options", $"The client options for reading and writing models.", typeof(ModelReaderWriterOptions));
private const string _privateAdditionalPropertiesPropertyDescription = "Keeps track of any properties unknown to the library.";
private const string _privateAdditionalPropertiesPropertyName = "_serializedAdditionalRawData";
private static readonly CSharpType _privateAdditionalPropertiesPropertyType = typeof(IDictionary<string, BinaryData>);
private readonly CSharpType _iJsonModelTInterface;
private readonly CSharpType? _iJsonModelObjectInterface;
private readonly CSharpType _iPersistableModelTInterface;
private readonly CSharpType? _iPersistableModelObjectInterface;
private readonly ModelTypeProvider _model;
private ModelTypeProvider _model;
private InputModelType _inputModel;
private readonly FieldDeclaration? _rawDataField;
private readonly bool _isStruct;

public MrwSerializationTypeProvider(ModelTypeProvider model)
public MrwSerializationTypeProvider(ModelTypeProvider model, InputModelType inputModel)
{
_model = model;
_inputModel = inputModel;
_isStruct = model.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Struct);
Name = model.Name;
Namespace = model.Namespace;
// Initialize the serialization interfaces
_iJsonModelTInterface = new CSharpType(typeof(IJsonModel<>), _model.Type);
_iJsonModelTInterface = new CSharpType(typeof(IJsonModel<>), model.Type);
_iJsonModelObjectInterface = _isStruct ? (CSharpType)typeof(IJsonModel<object>) : null;
_iPersistableModelTInterface = new CSharpType(typeof(IPersistableModel<>), _model.Type);
_iPersistableModelTInterface = new CSharpType(typeof(IPersistableModel<>), model.Type);
_iPersistableModelObjectInterface = _isStruct ? (CSharpType)typeof(IPersistableModel<object>) : null;
_rawDataField = BuildRawDataField();

Name = model.Name;
Namespace = model.Namespace;
}

protected override TypeSignatureModifiers GetDeclarationModifiers() => _model.DeclarationModifiers;
Expand All @@ -44,7 +55,79 @@ public MrwSerializationTypeProvider(ModelTypeProvider model)
public override string Namespace { get; }

/// <summary>
/// Builds the serialization methods for the model. If the serialization supports JSON, it will build the JSON serialization methods.
/// Builds the fields for the model by adding the raw data field for serialization.
/// </summary>
/// <returns>The list of <see cref="FieldDeclaration"/> for the model.</returns>
protected override FieldDeclaration[] BuildFields()
{
return _rawDataField != null ? [_rawDataField] : Array.Empty<FieldDeclaration>();
}

protected override CSharpMethod[] BuildConstructors()
{
List<CSharpMethod> constructors = new List<CSharpMethod>();
bool serializationCtorParamsMatch = false;
bool ctorWithNoParamsExist = false;
CSharpMethod serializationConstructor = BuildSerializationConstructor();

foreach (var ctor in _model.Constructors)
{
var initializationCtorParams = ctor.Signature.Parameters;

// Check if the model constructor has no parameters
if (!ctorWithNoParamsExist && !initializationCtorParams.Any())
{
ctorWithNoParamsExist = true;
}


if (!serializationCtorParamsMatch)
{
// Check if the model constructor parameters match the serialization constructor parameters
if (initializationCtorParams.SequenceEqual(
serializationConstructor.Signature.Parameters, Parameter.EqualityComparerByType))
{
serializationCtorParamsMatch = true;
}
}
}

// Add the serialization constructor if it doesn't match any of the existing constructors
if (!serializationCtorParamsMatch)
{
constructors.Add(serializationConstructor);
}

// Add an empty constructor if the model doesn't have one
if (!ctorWithNoParamsExist)
{
constructors.Add(BuildEmptyConstructor());
}

return constructors.ToArray();
}

/// <summary>
/// Builds the raw data field for the model to be used for serialization.
/// </summary>
/// <returns>The constructed <see cref="FieldDeclaration"/> if the model should generate the field.</returns>
private FieldDeclaration? BuildRawDataField()
{
if (_isStruct)
{
return null;
}

var fieldDeclaration = new FieldDeclaration(
modifiers: FieldModifiers.Private,
type: _privateAdditionalPropertiesPropertyType,
name: _privateAdditionalPropertiesPropertyName);

return fieldDeclaration;
}

/// <summary>
/// Builds the serialization methods for the model.
/// </summary>
/// <returns>A list of serialization and deserialization methods for the model.</returns>
protected override CSharpMethod[] BuildMethods()
Expand Down Expand Up @@ -90,7 +173,7 @@ internal CSharpMethod BuildJsonModelWriteMethod()
// void IJsonModel<T>.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options)
return new CSharpMethod
(
new MethodSignature(nameof(IJsonModel<object>.Write), null, null, MethodSignatureModifiers.None, null, null, new[] { utf8JsonWriterParameter, SerializationOptionsParameter }, ExplicitInterface: _iJsonModelTInterface),
new MethodSignature(nameof(IJsonModel<object>.Write), null, null, MethodSignatureModifiers.None, null, null, new[] { utf8JsonWriterParameter, _serializationOptionsParameter }, ExplicitInterface: _iJsonModelTInterface),
// TO-DO: Add body for json properties' serialization https://github.com/microsoft/typespec/issues/3330
Snippet.EmptyStatement
);
Expand All @@ -106,7 +189,7 @@ internal CSharpMethod BuildJsonModelCreateMethod()
var typeOfT = GetModelArgumentType(_iJsonModelTInterface);
return new CSharpMethod
(
new MethodSignature(nameof(IJsonModel<object>.Create), null, null, MethodSignatureModifiers.None, typeOfT, null, new[] { utf8JsonReaderParameter, SerializationOptionsParameter }, ExplicitInterface: _iJsonModelTInterface),
new MethodSignature(nameof(IJsonModel<object>.Create), null, null, MethodSignatureModifiers.None, typeOfT, null, new[] { utf8JsonReaderParameter, _serializationOptionsParameter }, ExplicitInterface: _iJsonModelTInterface),
// TO-DO: Call the base model ctor for now until the model properties are serialized https://github.com/microsoft/typespec/issues/3330
Snippet.Return(new NewInstanceExpression(typeOfT, Array.Empty<ValueExpression>()))
);
Expand All @@ -121,7 +204,7 @@ internal CSharpMethod BuildIModelWriteMethod()
var returnType = typeof(BinaryData);
return new CSharpMethod
(
new MethodSignature(nameof(IPersistableModel<object>.Write), null, null, MethodSignatureModifiers.None, returnType, null, new[] { SerializationOptionsParameter }, ExplicitInterface: _iPersistableModelTInterface),
new MethodSignature(nameof(IPersistableModel<object>.Write), null, null, MethodSignatureModifiers.None, returnType, null, new[] { _serializationOptionsParameter }, ExplicitInterface: _iPersistableModelTInterface),
// TO-DO: Call the base model ctor for now until the model properties are serialized https://github.com/microsoft/typespec/issues/3330
Snippet.Return(new NewInstanceExpression(returnType, [Snippet.Literal(_iPersistableModelTInterface.Name)]))
);
Expand All @@ -137,7 +220,7 @@ internal CSharpMethod BuildIModelCreateMethod()
var typeOfT = GetModelArgumentType(_iPersistableModelTInterface);
return new CSharpMethod
(
new MethodSignature(nameof(IPersistableModel<object>.Create), null, null, MethodSignatureModifiers.None, typeOfT, null, new[] { dataParameter, SerializationOptionsParameter }, ExplicitInterface: _iPersistableModelTInterface),
new MethodSignature(nameof(IPersistableModel<object>.Create), null, null, MethodSignatureModifiers.None, typeOfT, null, new[] { dataParameter, _serializationOptionsParameter }, ExplicitInterface: _iPersistableModelTInterface),
// TO-DO: Call the base model ctor for now until the model properties are serialized https://github.com/microsoft/typespec/issues/3330
Snippet.Return(new NewInstanceExpression(typeOfT, Array.Empty<ValueExpression>()))
);
Expand All @@ -152,11 +235,99 @@ internal CSharpMethod BuildIModelGetFormatFromOptionsMethod()
// ModelReaderWriterFormat IPersistableModel<T>.GetFormatFromOptions(ModelReaderWriterOptions options)
return new CSharpMethod
(
new MethodSignature(nameof(IPersistableModel<object>.GetFormatFromOptions), null, null, MethodSignatureModifiers.None, typeof(string), null, new[] { SerializationOptionsParameter }, ExplicitInterface: _iPersistableModelTInterface),
new MethodSignature(nameof(IPersistableModel<object>.GetFormatFromOptions), null, null, MethodSignatureModifiers.None, typeof(string), null, new[] { _serializationOptionsParameter }, ExplicitInterface: _iPersistableModelTInterface),
jsonWireFormat
);
}

/// <summary>
/// Builds the serialization constructor for the model.
/// </summary>
/// <returns>The constructed serialization constructor.</returns>
internal CSharpMethod BuildSerializationConstructor()
{
var serializationCtorParameters = BuildSerializationConstructorParameters();

return new CSharpMethod(
signature: new ConstructorSignature(
Type,
$"Initializes a new instance of {Type:C}",
null,
MethodSignatureModifiers.Internal,
serializationCtorParameters),
bodyStatements: new MethodBodyStatement[]
{
GetPropertyInitializers(serializationCtorParameters)
});
}

private MethodBodyStatement GetPropertyInitializers(IReadOnlyList<Parameter> parameters)
{
List<MethodBodyStatement> methodBodyStatements = new();

foreach (var param in parameters)
{
if (param.Name == _rawDataField?.Name.ToVariableName())
{
methodBodyStatements.Add(Snippet.Assign(new MemberExpression(null, _rawDataField.Name), new ParameterReferenceSnippet(param)));
continue;
}

ValueExpression initializationValue = new ParameterReferenceSnippet(param);
m-nash marked this conversation as resolved.
Show resolved Hide resolved
var initializationStatement = Snippet.Assign(new MemberExpression(null, param.Name.FirstCharToUpperCase()), initializationValue);
if (initializationStatement != null)
{
methodBodyStatements.Add(initializationStatement);
}
}

return methodBodyStatements;
}

/// <summary>
/// Builds the parameters for the serialization constructor by iterating through the input model properties.
/// It then adds raw data field to the constructor if it doesn't already exist in the list of constructed parameters.
/// </summary>
/// <returns>The list of parameters for the serialization parameter.</returns>
private List<Parameter> BuildSerializationConstructorParameters()
{
List<Parameter> constructorParameters = new List<Parameter>();
bool shouldAddRawDataField = _rawDataField != null;

foreach (var property in _inputModel.Properties)
{
var parameter = new Parameter(property)
{
Validation = ParameterValidationType.None,
};
constructorParameters.Add(parameter);

if (shouldAddRawDataField && string.Equals(parameter.Name, _rawDataField?.Name, StringComparison.OrdinalIgnoreCase))
{
shouldAddRawDataField = false;
}
}

// Append the raw data field if it doesn't already exist in the constructor parameters
if (shouldAddRawDataField && _rawDataField != null)
{
constructorParameters.Add(new Parameter(
_rawDataField.Name.ToVariableName(),
FormattableStringHelpers.FromString(_privateAdditionalPropertiesPropertyDescription),
_rawDataField.Type));
}

return constructorParameters;
}

private CSharpMethod BuildEmptyConstructor()
{
var accessibility = _isStruct ? MethodSignatureModifiers.Public : MethodSignatureModifiers.Internal;
return new CSharpMethod(
signature: new ConstructorSignature(Type, $"Initializes a new instance of {Type:C} for deserialization.", null, accessibility, Array.Empty<Parameter>()),
bodyStatements: new MethodBodyStatement());
}

/// <summary>
/// Attempts to get the model argument type from the model interface.
/// </summary>
Expand Down

This file was deleted.

Loading
Loading