Skip to content

Commit 8f56c6b

Browse files
MackinnonBuckCopilot
authored andcommitted
refactor: simplify C# RPC class tracking
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 8f5f567 commit 8f56c6b

File tree

1 file changed

+21
-33
lines changed

1 file changed

+21
-33
lines changed

scripts/codegen/csharp.ts

Lines changed: 21 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -701,7 +701,6 @@ export async function generateSessionEvents(schemaPath?: string): Promise<void>
701701
// RPC TYPES
702702
// ══════════════════════════════════════════════════════════════════════════════
703703

704-
let emittedRpcClasses = new Set<string>();
705704
let emittedRpcClassSchemas = new Map<string, string>();
706705
let experimentalRpcTypes = new Set<string>();
707706
let rpcKnownTypes = new Map<string, string>();
@@ -734,20 +733,6 @@ function stableStringify(value: unknown): string {
734733
return JSON.stringify(value);
735734
}
736735

737-
function chooseRpcClassName(preferredName: string, fallbackName: string, schema: JSONSchema7): string {
738-
const schemaKey = stableStringify(schema);
739-
const existingPreferred = emittedRpcClassSchemas.get(preferredName);
740-
if (!existingPreferred || existingPreferred === schemaKey) return preferredName;
741-
742-
let candidate = fallbackName;
743-
let suffix = 2;
744-
while (true) {
745-
const existing = emittedRpcClassSchemas.get(candidate);
746-
if (!existing || existing === schemaKey) return candidate;
747-
candidate = `${fallbackName}${suffix++}`;
748-
}
749-
}
750-
751736
function resolveRpcType(schema: JSONSchema7, isRequired: boolean, parentClassName: string, propName: string, classes: string[]): string {
752737
// Handle anyOf: [T, null] → T? (nullable typed property)
753738
if (schema.anyOf) {
@@ -770,32 +755,39 @@ function resolveRpcType(schema: JSONSchema7, isRequired: boolean, parentClassNam
770755
if (schema.type === "array" && schema.items) {
771756
const items = schema.items as JSONSchema7;
772757
if (items.type === "object" && items.properties) {
773-
const defaultName = (items.title as string) ?? singularPascal(propName);
774-
const contextualName = `${parentClassName}${defaultName}`;
775-
const itemClass = chooseRpcClassName(defaultName, contextualName, items);
776-
if (!emittedRpcClasses.has(itemClass)) classes.push(emitRpcClass(itemClass, items, "public", classes));
777-
return isRequired ? `IList<${itemClass}>` : `IList<${itemClass}>?`;
758+
const itemClass = (items.title as string) ?? singularPascal(propName);
759+
classes.push(emitRpcClass(itemClass, items, "public", classes));
760+
return isRequired ? `List<${itemClass}>` : `List<${itemClass}>?`;
778761
}
779762
const itemType = schemaTypeToCSharp(items, true, rpcKnownTypes);
780-
return isRequired ? `IList<${itemType}>` : `IList<${itemType}>?`;
763+
return isRequired ? `List<${itemType}>` : `List<${itemType}>?`;
781764
}
782765
if (schema.type === "object" && schema.additionalProperties && typeof schema.additionalProperties === "object") {
783766
const vs = schema.additionalProperties as JSONSchema7;
784767
if (vs.type === "object" && vs.properties) {
785768
const valClass = `${parentClassName}${propName}Value`;
786769
classes.push(emitRpcClass(valClass, vs, "public", classes));
787-
return isRequired ? `IDictionary<string, ${valClass}>` : `IDictionary<string, ${valClass}>?`;
770+
return isRequired ? `Dictionary<string, ${valClass}>` : `Dictionary<string, ${valClass}>?`;
788771
}
789772
const valueType = schemaTypeToCSharp(vs, true, rpcKnownTypes);
790-
return isRequired ? `IDictionary<string, ${valueType}>` : `IDictionary<string, ${valueType}>?`;
773+
return isRequired ? `Dictionary<string, ${valueType}>` : `Dictionary<string, ${valueType}>?`;
791774
}
792775
return schemaTypeToCSharp(schema, isRequired, rpcKnownTypes);
793776
}
794777

795778
function emitRpcClass(className: string, schema: JSONSchema7, visibility: "public" | "internal", extraClasses: string[]): string {
796-
if (emittedRpcClasses.has(className)) return "";
797-
emittedRpcClasses.add(className);
798-
emittedRpcClassSchemas.set(className, stableStringify(schema));
779+
const schemaKey = stableStringify(schema);
780+
const existingSchema = emittedRpcClassSchemas.get(className);
781+
if (existingSchema) {
782+
if (existingSchema !== schemaKey) {
783+
throw new Error(
784+
`Conflicting RPC class name "${className}" for different schemas. Add a schema title/withTypeName to disambiguate.`
785+
);
786+
}
787+
return "";
788+
}
789+
790+
emittedRpcClassSchemas.set(className, schemaKey);
799791

800792
const requiredSet = new Set(schema.required || []);
801793
const lines: string[] = [];
@@ -824,12 +816,9 @@ function emitRpcClass(className: string, schema: JSONSchema7, visibility: "publi
824816
if (isReq && !csharpType.endsWith("?")) {
825817
if (csharpType === "string") defaultVal = " = string.Empty;";
826818
else if (csharpType === "object") defaultVal = " = null!;";
827-
else if (csharpType.startsWith("IList<")) {
819+
else if (csharpType.startsWith("List<") || csharpType.startsWith("Dictionary<")) {
828820
propAccessors = "{ get => field ??= []; set; }";
829-
} else if (csharpType.startsWith("IDictionary<")) {
830-
const concreteType = csharpType.replace("IDictionary<", "Dictionary<");
831-
propAccessors = `{ get => field ??= new ${concreteType}(); set; }`;
832-
} else if (emittedRpcClasses.has(csharpType)) {
821+
} else if (emittedRpcClassSchemas.has(csharpType)) {
833822
propAccessors = "{ get => field ??= new(); set; }";
834823
}
835824
}
@@ -1200,7 +1189,6 @@ function emitClientSessionApiRegistration(clientSchema: Record<string, unknown>,
12001189
}
12011190

12021191
function generateRpcCode(schema: ApiSchema): string {
1203-
emittedRpcClasses.clear();
12041192
emittedRpcClassSchemas.clear();
12051193
experimentalRpcTypes.clear();
12061194
rpcKnownTypes.clear();
@@ -1246,7 +1234,7 @@ internal static class Diagnostics
12461234
if (clientSessionParts.length > 0) lines.push(...clientSessionParts, "");
12471235

12481236
// Add JsonSerializerContext for AOT/trimming support
1249-
const typeNames = [...emittedRpcClasses].sort();
1237+
const typeNames = [...emittedRpcClassSchemas.keys()].sort();
12501238
if (typeNames.length > 0) {
12511239
lines.push(`[JsonSourceGenerationOptions(`);
12521240
lines.push(` JsonSerializerDefaults.Web,`);

0 commit comments

Comments
 (0)