Skip to content

Commit a196b18

Browse files
Smaug123claude
andcommitted
Allocate anonymous type names from an occupied-name set
Seeding the anonymous-name counter to the largest spec-defined Type{N} overflows on a valid schema named Type2147483647: the next Interlocked.Increment wraps to Int32.MinValue and produces a type declared (sanitised) as Type2147483648 but referenced as Type-2147483648, failing with FS0010. Instead of seeding, the allocator now skips over a set of taken (sanitised) definition/response names, so no name can push the counter anywhere near overflow. The ConsumePlugin regression spec now also defines Type2147483647 (structurally distinct from Type1, since identical shapes are collapsed into one generated type), so both the collision and the overflow fail the build if reintroduced. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 57a533c commit a196b18

3 files changed

Lines changed: 43 additions & 23 deletions

File tree

ConsumePlugin/GeneratedSwaggerAnonymousTypes.fs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,15 @@ type Type1 =
2323
Name : string option
2424
}
2525

26+
[<JsonParse true ; JsonSerialize true>]
27+
type Type2147483647 =
28+
{
29+
[<System.Text.Json.Serialization.JsonExtensionData>]
30+
AdditionalProperties : System.Collections.Generic.Dictionary<string, System.Text.Json.Nodes.JsonNode>
31+
[<System.Text.Json.Serialization.JsonPropertyName "wouldOverflowASeededCounter">]
32+
WouldOverflowASeededCounter : string option
33+
}
34+
2635
[<JsonParse true ; JsonSerialize true>]
2736
type Type2 =
2837
{

ConsumePlugin/swagger-anonymous-types.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@
5050
"type": "string"
5151
}
5252
}
53+
},
54+
"Type2147483647": {
55+
"type": "object",
56+
"properties": {
57+
"wouldOverflowASeededCounter": {
58+
"type": "string"
59+
}
60+
}
5361
}
5462
},
5563
"responses": {}

WoofWare.Myriad.Plugins/SwaggerClientGenerator.fs

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,10 @@ module internal SwaggerClientGenerator =
6969
| SwaggerV2.Definition.File -> SynType.createLongIdent' [ "System" ; "IO" ; "Stream" ] |> Some
7070

7171
/// Returns None if we lacked the information required to do this.
72+
/// nextAnonymousTypeName must return a fresh unused type name on each call.
7273
/// bigCache is a map of e.g. {"securityDefinition": {Defn : F# type}}.
7374
let rec defnToType
74-
(anonymousTypeCount : int ref)
75+
(nextAnonymousTypeName : unit -> string)
7576
(handlesMap : Dictionary<string, TypeEntry>)
7677
(bigCache : Dictionary<string, Dictionary<SwaggerV2.Definition, TypeEntry>>)
7778
(thisKey : string)
@@ -154,7 +155,7 @@ module internal SwaggerClientGenerator =
154155
|> Some
155156
| None ->
156157

157-
let defn' = defnToType anonymousTypeCount handlesMap bigCache thisKey None defn
158+
let defn' = defnToType nextAnonymousTypeName handlesMap bigCache thisKey None defn
158159

159160
match defn' with
160161
| None -> None
@@ -200,7 +201,7 @@ module internal SwaggerClientGenerator =
200201
|> Some
201202
| Some SwaggerV2.AdditionalProperties.Never -> Some []
202203
| Some (SwaggerV2.AdditionalProperties.Constrained defn) ->
203-
let defn' = defnToType anonymousTypeCount handlesMap bigCache thisKey None defn
204+
let defn' = defnToType nextAnonymousTypeName handlesMap bigCache thisKey None defn
204205

205206
match defn' with
206207
| None -> None
@@ -234,7 +235,7 @@ module internal SwaggerClientGenerator =
234235

235236
let fSharpTypeName =
236237
match typeName with
237-
| None -> $"Type%i{Interlocked.Increment anonymousTypeCount}"
238+
| None -> nextAnonymousTypeName ()
238239
| Some typeName -> typeName
239240

240241
let properties = additionalProperties @ namedProperties
@@ -278,7 +279,8 @@ module internal SwaggerClientGenerator =
278279
defn |> Some
279280

280281
| SwaggerV2.Definition.Array elt ->
281-
let child = defnToType anonymousTypeCount handlesMap bigCache thisKey None elt.Items
282+
let child =
283+
defnToType nextAnonymousTypeName handlesMap bigCache thisKey None elt.Items
282284

283285
match child with
284286
| None -> None
@@ -540,35 +542,36 @@ module internal SwaggerV2Generator =
540542
(0, bigCache) ||> Seq.fold (fun count (KeyValue (_, v)) -> count + v.Count)
541543

542544
let byHandle = Dictionary ()
543-
let anonymousTypeCount = ref 0
544545

545546
// A spec is free to define types whose (sanitised) names look like the
546-
// "Type{N}" names we invent for anonymous inline schemas; start counting
547-
// past any such name so we never collide with them.
548-
let namedTypes =
549-
seq {
550-
for KeyValue (k, _) in contents.Definitions do
551-
yield k
552-
553-
for KeyValue (k, _) in contents.Responses do
554-
yield k
555-
}
547+
// "Type{N}" names we invent for anonymous inline schemas (including
548+
// pathological ones like "Type2147483647", which would overflow a
549+
// seeded counter); skip over any name that's already taken.
550+
let takenNames = HashSet<string> ()
551+
552+
for KeyValue (k, _) in contents.Definitions do
553+
takenNames.Add (Ident.createSanitisedTypeName k).idText |> ignore<bool>
554+
555+
for KeyValue (k, _) in contents.Responses do
556+
takenNames.Add (Ident.createSanitisedTypeName k).idText |> ignore<bool>
557+
558+
let anonymousTypeCount = ref 0
559+
560+
let nextAnonymousTypeName () : string =
561+
let mutable candidate = $"Type%i{Interlocked.Increment anonymousTypeCount}"
556562

557-
for name in namedTypes do
558-
let name = (Ident.createSanitisedTypeName name).idText
563+
while takenNames.Contains candidate do
564+
candidate <- $"Type%i{Interlocked.Increment anonymousTypeCount}"
559565

560-
if name.StartsWith ("Type", System.StringComparison.Ordinal) then
561-
match System.Int32.TryParse (name.Substring "Type".Length) with
562-
| true, n -> anonymousTypeCount.Value <- max anonymousTypeCount.Value n
563-
| false, _ -> ()
566+
candidate
564567

565568
let rec go (contents : ((string option * SwaggerV2.Definition) * string) list) =
566569
let lastRound = countAll ()
567570

568571
contents
569572
|> List.filter (fun ((name, defn), defnClass) ->
570573
let doIt =
571-
SwaggerClientGenerator.defnToType anonymousTypeCount byHandle bigCache defnClass name defn
574+
SwaggerClientGenerator.defnToType nextAnonymousTypeName byHandle bigCache defnClass name defn
572575

573576
match doIt with
574577
| None -> true

0 commit comments

Comments
 (0)