Skip to content

Commit 78a5d95

Browse files
authored
Remove unexpected methods from ModelFactory for management (#51205)
* Add ModelFacotryVisitor to omit methods for mgmt * fix for safe flatten * Update modelFactory for safe-flatten * Address comments
1 parent 481678d commit 78a5d95

File tree

9 files changed

+175
-80
lines changed

9 files changed

+175
-80
lines changed

eng/packages/http-client-csharp-mgmt/generator/Azure.Generator.Management/src/ManagementClientGenerator.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ protected override void Configure()
5858
AddVisitor(new SerializationVisitor());
5959
AddVisitor(new PaginationVisitor());
6060
AddVisitor(new SafeFlattenVisitor());
61+
AddVisitor(new ModelFactoryVisitor());
6162
}
6263
}
6364
}

eng/packages/http-client-csharp-mgmt/generator/Azure.Generator.Management/src/ManagementOutputLibrary.cs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ namespace Azure.Generator.Management
1818
{
1919
/// <inheritdoc/>
2020
public class ManagementOutputLibrary : AzureOutputLibrary
21-
{ private ManagementLongRunningOperationProvider? _armOperation;
21+
{
22+
private ManagementLongRunningOperationProvider? _armOperation;
2223
internal ManagementLongRunningOperationProvider ArmOperation => _armOperation ??= new ManagementLongRunningOperationProvider(false);
2324

2425
private ManagementLongRunningOperationProvider? _genericArmOperation;
@@ -28,6 +29,55 @@ public class ManagementOutputLibrary : AzureOutputLibrary
2829
private HashSet<CSharpType>? _resourceTypes;
2930
private HashSet<CSharpType> ResourceTypes => _resourceTypes ??= BuildResourceModels();
3031

32+
// TODO: replace this with CSharpType to TypeProvider mapping and move this logic to ModelFactoryVisitor
33+
private HashSet<CSharpType>? _modelFactoryModels;
34+
private HashSet<CSharpType> ModelFactoryModels => _modelFactoryModels ??= BuildModelFactoryModels();
35+
internal HashSet<CSharpType> BuildModelFactoryModels()
36+
{
37+
var result = new HashSet<CSharpType>();
38+
foreach (var inputModel in ManagementClientGenerator.Instance.InputLibrary.InputNamespace.Models)
39+
{
40+
var model = ManagementClientGenerator.Instance.TypeFactory.CreateModel(inputModel);
41+
if (model is not null && IsModelFactoryModel(model))
42+
{
43+
result.Add(model.Type);
44+
}
45+
}
46+
return result;
47+
}
48+
49+
private static bool IsModelFactoryModel(ModelProvider model)
50+
{
51+
// A model is a model factory model if it is public and it has at least one public property without a setter.
52+
return model.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Public) && EnumerateAllPublicProperties(model).Any(prop => !prop.Body.HasSetter);
53+
54+
IEnumerable<PropertyProvider> EnumerateAllPublicProperties(ModelProvider current)
55+
{
56+
var currentModel = current;
57+
foreach (var property in currentModel.Properties)
58+
{
59+
if (property.Modifiers.HasFlag(MethodSignatureModifiers.Public))
60+
{
61+
yield return property;
62+
}
63+
}
64+
65+
while (currentModel.BaseModelProvider is not null)
66+
{
67+
currentModel = currentModel.BaseModelProvider;
68+
foreach (var property in currentModel.Properties)
69+
{
70+
if (property.Modifiers.HasFlag(MethodSignatureModifiers.Public))
71+
{
72+
yield return property;
73+
}
74+
}
75+
}
76+
}
77+
}
78+
79+
internal bool IsModelFactoryModelType(CSharpType type) => ModelFactoryModels.Contains(type);
80+
3181
private HashSet<CSharpType> BuildResourceModels()
3282
{
3383
var resourceTypes = new HashSet<CSharpType>();
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using Microsoft.TypeSpec.Generator.ClientModel;
5+
using Microsoft.TypeSpec.Generator.Providers;
6+
using System.Collections.Generic;
7+
8+
namespace Azure.Generator.Management.Visitors
9+
{
10+
internal class ModelFactoryVisitor : ScmLibraryVisitor
11+
{
12+
protected override TypeProvider? VisitType(TypeProvider type)
13+
{
14+
if (type is ModelFactoryProvider modelFactory)
15+
{
16+
var updatedMethods = new List<MethodProvider>();
17+
foreach (var method in modelFactory.Methods)
18+
{
19+
var returnType = method.Signature.ReturnType;
20+
if (returnType is not null && ManagementClientGenerator.Instance.OutputLibrary.IsModelFactoryModelType(returnType))
21+
{
22+
updatedMethods.Add(method);
23+
}
24+
}
25+
modelFactory.Update(methods: updatedMethods);
26+
return modelFactory;
27+
}
28+
return base.VisitType(type);
29+
}
30+
}
31+
}

eng/packages/http-client-csharp-mgmt/generator/Azure.Generator.Management/src/Visitors/SafeFlattenVisitor.cs

Lines changed: 85 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Microsoft.TypeSpec.Generator.Primitives;
88
using Microsoft.TypeSpec.Generator.Providers;
99
using Microsoft.TypeSpec.Generator.Statements;
10+
using System;
1011
using System.Collections.Generic;
1112
using System.Linq;
1213
using static Microsoft.TypeSpec.Generator.Snippets.Snippet;
@@ -15,8 +16,8 @@ namespace Azure.Generator.Management.Visitors
1516
{
1617
internal class SafeFlattenVisitor : ScmLibraryVisitor
1718
{
18-
private Dictionary<ModelProvider, (HashSet<PropertyProvider> FlattenedProperties, HashSet<PropertyProvider> InternalizedProperties)> _flattenedModels = new();
19-
private HashSet<CSharpType> _flattenedTypes = new();
19+
private readonly Dictionary<ModelProvider, (HashSet<PropertyProvider> FlattenedProperties, HashSet<PropertyProvider> InternalizedProperties)> _flattenedModels = new();
20+
private readonly Dictionary<CSharpType, Dictionary<CSharpType, PropertyProvider>> _flattenedModelTypes = new();
2021

2122
// TODO: we can't check property count of property types in VisitType since we don't have TypeProvider from CSharpType.
2223
// Once we have CSharpType to TypeProvider mapping, we can remove this and have this logic in VisitType instead
@@ -27,6 +28,7 @@ internal class SafeFlattenVisitor : ScmLibraryVisitor
2728
return null;
2829
}
2930

31+
var flattenedPropertyMap = new Dictionary<CSharpType, PropertyProvider>();
3032
var flattenedProperties = new HashSet<PropertyProvider>();
3133
var internalizedProperties = new HashSet<PropertyProvider>();
3234
foreach (var property in model.Properties)
@@ -43,7 +45,6 @@ internal class SafeFlattenVisitor : ScmLibraryVisitor
4345
// make the current property internal
4446
var internalSingleProperty = type!.Properties.Single(p => p.Type.AreNamesEqual(propertyTypeProvider.Type)); // type equal not working here, so we use AreNamesEqual
4547
internalizedProperties.Add(internalSingleProperty);
46-
_flattenedTypes.Add(propertyTypeProvider.Type!);
4748

4849
// flatten the single property to public and associate it with the internal property
4950
var flattenPropertyName = $"{singleProperty.Name}"; // TODO: handle name conflicts
@@ -60,19 +61,99 @@ internal class SafeFlattenVisitor : ScmLibraryVisitor
6061
});
6162
var flattenedProperty = new PropertyProvider(singleProperty.Description, singleProperty.Modifiers, singleProperty.Type, flattenPropertyName, flattenPropertyBody, type, singleProperty.ExplicitInterface, singleProperty.WireInfo, singleProperty.Attributes);
6263
flattenedProperties.Add(flattenedProperty);
64+
flattenedPropertyMap.Add(internalSingleProperty.Type, flattenedProperty);
6365
}
6466
}
6567
}
6668
if (flattenedProperties.Count > 0)
6769
{
6870
_flattenedModels[type!] = (flattenedProperties, internalizedProperties);
71+
_flattenedModelTypes.Add(type.Type, flattenedPropertyMap);
6972
}
7073
return base.PreVisitModel(model, type);
7174
}
7275

7376
protected override TypeProvider? VisitType(TypeProvider type)
7477
{
75-
if (type is ModelProvider model && _flattenedModels.TryGetValue(model, out var value))
78+
if (type is ModelProvider model)
79+
{
80+
UpdateModel(type, model);
81+
}
82+
83+
if (type is ModelFactoryProvider modelFactory)
84+
{
85+
// If this is a model factory, we need to ensure that the flattened properties are also included in the factory methods.
86+
UpdateModelFactory(modelFactory);
87+
}
88+
89+
return base.VisitType(type);
90+
}
91+
92+
private void UpdateModelFactory(ModelFactoryProvider modelFactory)
93+
{
94+
foreach (var method in modelFactory.Methods)
95+
{
96+
var returnType = method.Signature.ReturnType;
97+
if (returnType is not null && _flattenedModelTypes.TryGetValue(returnType, out var value))
98+
{
99+
UpdateModelFactoryMethod(method, returnType, value);
100+
}
101+
}
102+
}
103+
104+
private void UpdateModelFactoryMethod(MethodProvider method, CSharpType returnType, Dictionary<CSharpType, PropertyProvider> propertyMap)
105+
{
106+
var updatedParameters = new List<ParameterProvider>(method.Signature.Parameters.Count);
107+
var updated = false;
108+
foreach (var parameter in method.Signature.Parameters)
109+
{
110+
if (propertyMap.TryGetValue(parameter.Type, out var flattenedProperty))
111+
{
112+
updated = true;
113+
var updatedParameter = flattenedProperty.AsParameter;
114+
if (flattenedProperty.Type.IsNullable)
115+
{
116+
updatedParameter.DefaultValue = Default; // Ensure that the default value is set to null for nullable types
117+
}
118+
updatedParameters.Add(updatedParameter);
119+
}
120+
else
121+
{
122+
updatedParameters.Add(parameter);
123+
}
124+
}
125+
if (updated)
126+
{
127+
method.Signature.Update(parameters: updatedParameters);
128+
129+
// The model factory method return a new instance of the model type, update the constructor arguments with the flattened properties of internalized properties.
130+
var instanceExpression = (method.BodyStatements?.OfType<ExpressionStatement>().Single()?.Expression as KeywordExpression)?.Expression as NewInstanceExpression;
131+
var updatedInstanceParamters = new List<ValueExpression>(instanceExpression!.Parameters.Count);
132+
foreach (var instanceParemter in instanceExpression!.Parameters)
133+
{
134+
// If the instance parameter is a variable expression, we can check if it is a flattened property and update it accordingly.
135+
if (instanceParemter is VariableExpression variable && propertyMap.TryGetValue(variable.Type, out var flattenedProperty))
136+
{
137+
updatedInstanceParamters.Add(
138+
new TernaryConditionalExpression(
139+
flattenedProperty.AsParameter.Is(Null),
140+
Default,
141+
New.Instance(
142+
variable.Type,
143+
[flattenedProperty.AsParameter, New.Instance(new CSharpType(typeof(Dictionary<,>), typeof(string), typeof(BinaryData)))]))); // TODO: handle additional parameters properly or should it be nullable?
144+
}
145+
else
146+
{
147+
updatedInstanceParamters.Add(instanceParemter);
148+
}
149+
}
150+
method.Update(signature: method.Signature, bodyStatements: Return(New.Instance(instanceExpression.Type!, updatedInstanceParamters)));
151+
}
152+
}
153+
154+
private void UpdateModel(TypeProvider type, ModelProvider model)
155+
{
156+
if (_flattenedModels.TryGetValue(model, out var value))
76157
{
77158
var (flattenedProperties, internalizedProperties) = value;
78159
type.Update(properties: [.. type.Properties, .. flattenedProperties]);
@@ -82,13 +163,6 @@ internal class SafeFlattenVisitor : ScmLibraryVisitor
82163
internalProperty.Update(modifiers: internalProperty.Modifiers & ~MethodSignatureModifiers.Public | MethodSignatureModifiers.Internal);
83164
}
84165
}
85-
86-
// Since we make the properties with flattend types internal, the flattened types will be internalized during post processing, We need to keep the flattened types public
87-
if (_flattenedTypes.Any(x => x.AreNamesEqual(type.Type)))
88-
{
89-
ManagementClientGenerator.Instance.AddTypeToKeep(type);
90-
}
91-
return base.VisitType(type);
92166
}
93167
}
94168
}

eng/packages/http-client-csharp-mgmt/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/MgmtTypeSpecModelFactory.cs

Lines changed: 3 additions & 64 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

eng/packages/http-client-csharp-mgmt/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/Models/BarSettingsProperties.Serialization.cs

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

eng/packages/http-client-csharp-mgmt/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/Models/BarSettingsProperties.cs

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)