openai/openai-dotnet

Public

mirrored from https://github.com/openai/openai-dotnetAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
mailinhphan/clientOptions

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

codegen/generator/src/Visitors/ConstructorFixupVisitor.cs

178lines · modecode

1using Microsoft.TypeSpec.Generator.ClientModel;
2using Microsoft.TypeSpec.Generator.Expressions;
3using Microsoft.TypeSpec.Generator.Primitives;
4using Microsoft.TypeSpec.Generator.Providers;
5using Microsoft.TypeSpec.Generator.Snippets;
6using Microsoft.TypeSpec.Generator.Statements;
7using System.Collections.Generic;
8using System.Linq;
9using static Microsoft.TypeSpec.Generator.Snippets.Snippet;
10
11namespace OpenAILibraryPlugin.Visitors;
12
13/// <summary>
14/// This visitor performs several in-place modifications of model-based type constructors:
15/// 1. All generated constructors ensure that collections are initialized (via null coalescence) in their bodies
16/// 2. All generated default constructors chain initialization to the generated serialization constructor
17/// </summary>
18public class ConstructorFixupVisitor : ScmLibraryVisitor
19{
20 private static readonly MethodBodyStatement CommentStatement
21 = new SingleLineCommentStatement("Plugin customization: ensure initialization of collections");
22
23 protected override TypeProvider? PostVisitType(TypeProvider type)
24 {
25 if (type is not ModelProvider modelProvider
26 || modelProvider.Type.IsValueType
27 || modelProvider.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Static))
28 {
29 return type;
30 }
31
32 List<ConstructorProvider> allGeneratedConstructors =
33 [
34 .. modelProvider.Constructors,
35 .. modelProvider.SerializationProviders.SelectMany(mrwProvider => mrwProvider.Constructors),
36 ];
37 List<ConstructorProvider> allConstructorsIncludingCustom =
38 [
39 .. allGeneratedConstructors,
40 .. (modelProvider?.CustomCodeView?.Constructors ?? [])
41 ];
42 ConstructorProvider? serializationConstructor = allConstructorsIncludingCustom.MaxBy(ctr => ctr.Signature.Parameters.Count);
43 ConstructorProvider? generatedDefaultConstructor = allGeneratedConstructors.FirstOrDefault(ctr => ctr.Signature.Parameters.Count == 0);
44
45 bool adjustmentPerformed = false;
46
47 foreach (ConstructorProvider constructor in allGeneratedConstructors)
48 {
49 adjustmentPerformed |= TryUpdateConstructorForCollectionInitialization(constructor);
50 }
51
52 // If any generated constructors were updated to perform additional initialization, OR if the type uses a discriminator, adjust the
53 // default constructor to chain to the serialization constructor that will produce an independently valid instance state.
54 if (generatedDefaultConstructor is not null
55 && (adjustmentPerformed || modelProvider?.DiscriminatorValueExpression is not null))
56 {
57 CSharpType? discriminatorType = modelProvider?.DiscriminatorValueExpression is MemberExpression enclosingDiscriminatorValueExpression
58 && enclosingDiscriminatorValueExpression.Inner is TypeReferenceExpression enclosingDiscriminatorTypeReferenceExpression
59 ? enclosingDiscriminatorTypeReferenceExpression.Type
60 : null;
61
62 List<ValueExpression> initializationExpressions = [];
63 foreach (ParameterProvider parameter in serializationConstructor?.Signature.Parameters ?? [])
64 {
65 initializationExpressions.Add(
66 parameter.Type == discriminatorType && modelProvider?.DiscriminatorValueExpression is not null
67 ? modelProvider.DiscriminatorValueExpression!
68 : parameter.Type.IsValueType
69 ? Default
70 : Null);
71 }
72
73 ConstructorSignature updatedSignature = new(
74 generatedDefaultConstructor.Signature.Type,
75 generatedDefaultConstructor.Signature.Description,
76 generatedDefaultConstructor.Signature.Modifiers,
77 generatedDefaultConstructor.Signature.Parameters,
78 generatedDefaultConstructor.Signature.Attributes,
79 new(false, initializationExpressions));
80 generatedDefaultConstructor.Update(signature: updatedSignature, bodyStatements: new MethodBodyStatements([]));
81 }
82
83 return type;
84 }
85
86 private static bool TryUpdateConstructorForCollectionInitialization(ConstructorProvider constructor)
87 {
88 IEnumerable<ParameterProvider> eligibleParameters
89 = constructor?.Signature.Parameters
90 .Where(parameter => parameter?.Type?.IsValueType == false
91 && parameter?.Name != "additionalBinaryDataProperties"
92 && (parameter?.Type?.IsList == true
93 || parameter?.Type?.IsCollection == true
94 || parameter?.Type?.IsDictionary == true
95 || parameter?.Type?.BaseType?.Name?.Contains("Collection") == true)) ?? [];
96
97 if (eligibleParameters.Any() != true)
98 {
99 return false;
100 }
101
102 List<MethodBodyStatement> bodyStatements
103 = constructor?.BodyStatements?.ToList() ?? [];
104
105 if (TryUpdateStatementsForParameters(bodyStatements, eligibleParameters))
106 {
107 bodyStatements.Insert(0, CommentStatement);
108 constructor?.Update(bodyStatements: bodyStatements);
109 return true;
110 }
111
112 return false;
113 }
114
115 private static bool TryUpdateStatementsForParameters(
116 List<MethodBodyStatement> statements,
117 IEnumerable<ParameterProvider> parameters)
118 {
119 bool changed = false;
120
121 for (int i = 0; i < statements.Count; i++)
122 {
123 changed |= TryUpdateStatementForParameters(
124 statements[i],
125 parameters,
126 out MethodBodyStatement handledStatement);
127 statements[i] = handledStatement;
128 }
129
130 return changed;
131 }
132
133 private static bool TryUpdateStatementForParameters(
134 MethodBodyStatement originalStatement,
135 IEnumerable<ParameterProvider> parameters,
136 out MethodBodyStatement handledStatement)
137 {
138 if (originalStatement is ExpressionStatement expressionStatement
139 && expressionStatement.Expression is AssignmentExpression assignmentExpression)
140 {
141 foreach (ParameterProvider parameter in parameters)
142 {
143 if (TryUpdateStatementForParameter(
144 assignmentExpression,
145 parameter,
146 out MethodBodyStatement? updatedStatement)
147 && updatedStatement is not null)
148 {
149 handledStatement = updatedStatement;
150 return true;
151 }
152 }
153 }
154 handledStatement = originalStatement;
155 return false;
156 }
157
158 private static bool TryUpdateStatementForParameter(
159 AssignmentExpression assignmentExpression,
160 ParameterProvider parameter,
161 out MethodBodyStatement? handledStatement)
162 {
163 if (assignmentExpression.Value.ToDisplayString() == parameter.Name)
164 {
165 ValueExpression nullFallbackExpression = parameter.Type.IsList || parameter.Type.IsDictionary
166 ? New.Instance(parameter.Type.PropertyInitializationType)
167 : New.Instance(parameter.Type);
168 ValueExpression coalescedValueExpression = assignmentExpression.Value
169 .NullCoalesce(nullFallbackExpression);
170 handledStatement = assignmentExpression.Variable
171 .Assign(coalescedValueExpression)
172 .Terminate();
173 return true;
174 }
175 handledStatement = null;
176 return false;
177 }
178}