Skip to content

Commit 5ddba51

Browse files
YamlProvider: preserve quoted scalar strings during type inference
In YAML, quoted scalars are explicitly strings by spec. Previously, with InferTypesFromValues=true (the default), JsonInference would re-infer values like "01234" as int even when they were explicitly quoted as strings in the YAML source. Fix: add a design-time parsing function (ParseToJsonValueForInference) that substitutes quoted scalars with a non-numeric sentinel string, so inference always returns string for them. Runtime parsing is unchanged; only the design-time inference path is affected. Update the expected signature for SimpleYaml.yaml to reflect that zip: "01234" is now correctly inferred as string instead of int. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 42f6d41 commit 5ddba51

3 files changed

Lines changed: 73 additions & 6 deletions

File tree

src/FSharp.Data.DesignTime/Yaml/YamlProvider.fs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,11 @@ type public YamlProvider(cfg: TypeProviderConfig) as this =
7777

7878
if sampleIsList then
7979
// If SampleIsList, parse as a YAML sequence or multiple documents
80-
match YamlDocument.ParseToJsonValue value with
80+
match YamlDocument.ParseToJsonValueForInference value with
8181
| JsonValue.Array items -> items
8282
| single -> [| single |]
8383
else
84-
[| YamlDocument.ParseToJsonValue value |]
84+
[| YamlDocument.ParseToJsonValueForInference value |]
8585

8686
samples
8787
|> Array.map (fun sampleJson ->

src/FSharp.Data.Yaml.Core/YamlDocument.fs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,62 @@ module internal YamlConversions =
9090
yaml.Load(reader)
9191
[| for doc in yaml.Documents -> yamlNodeToJsonValue doc.RootNode |]
9292

93+
// Like yamlNodeToJsonValue, but for design-time type inference only.
94+
// In YAML, quoted scalars are always strings by spec (the quoting is an explicit type annotation).
95+
// When InferTypesFromValues=true, JsonInference would otherwise re-infer "01234" as int.
96+
// We prevent this by substituting a guaranteed non-numeric sentinel for quoted scalars,
97+
// so inference always returns string for them. Runtime parsing still uses yamlNodeToJsonValue.
98+
let rec yamlNodeToJsonValueForInference (node: YamlNode) : JsonValue =
99+
match node with
100+
| :? YamlMappingNode as mapping ->
101+
let props =
102+
[| for kvp in mapping.Children do
103+
let key =
104+
match kvp.Key with
105+
| :? YamlScalarNode as s -> s.Value
106+
| other -> other.ToString()
107+
108+
yield (key, yamlNodeToJsonValueForInference kvp.Value) |]
109+
110+
JsonValue.Record props
111+
112+
| :? YamlSequenceNode as sequence ->
113+
let elements =
114+
[| for item in sequence.Children -> yamlNodeToJsonValueForInference item |]
115+
116+
JsonValue.Array elements
117+
118+
| :? YamlScalarNode as scalar ->
119+
let value = scalar.Value
120+
121+
if value = null then
122+
JsonValue.Null
123+
else
124+
match scalar.Style with
125+
| YamlDotNet.Core.ScalarStyle.SingleQuoted
126+
| YamlDotNet.Core.ScalarStyle.DoubleQuoted
127+
| YamlDotNet.Core.ScalarStyle.Literal
128+
| YamlDotNet.Core.ScalarStyle.Folded ->
129+
// Quoted scalar: YAML spec says this is always a string.
130+
// Use a non-numeric sentinel so InferTypesFromValues does not re-infer
131+
// the value content as int/bool/date/etc.
132+
JsonValue.String "quoted-string"
133+
| _ ->
134+
// Plain scalars: same as runtime conversion
135+
yamlNodeToJsonValue node
136+
137+
| _ -> JsonValue.Null
138+
139+
let parseYamlForInference (text: string) : JsonValue =
140+
let yaml = YamlStream()
141+
use reader = new StringReader(text)
142+
yaml.Load(reader)
143+
144+
if yaml.Documents.Count = 0 then
145+
JsonValue.Null
146+
else
147+
yamlNodeToJsonValueForInference yaml.Documents.[0].RootNode
148+
93149

94150
// --------------------------------------------------------------------------------------
95151
// YamlDocument - implements IJsonDocument so it can be used with JSON-based generated code
@@ -141,6 +197,17 @@ type YamlDocument =
141197
IsError = false)>]
142198
static member ParseToJsonValueArray(text: string) : JsonValue[] = YamlConversions.parseYamlDocuments text
143199

200+
/// <exclude />
201+
/// Design-time only: like ParseToJsonValue but quoted scalars are represented with a
202+
/// non-numeric sentinel string so that InferTypesFromValues does not re-infer them.
203+
[<EditorBrowsableAttribute(EditorBrowsableState.Never)>]
204+
[<CompilerMessageAttribute("This method is intended for use in generated code only.",
205+
10001,
206+
IsHidden = true,
207+
IsError = false)>]
208+
static member ParseToJsonValueForInference(text: string) : JsonValue =
209+
YamlConversions.parseYamlForInference text
210+
144211
/// <exclude />
145212
[<EditorBrowsableAttribute(EditorBrowsableState.Never)>]
146213
[<CompilerMessageAttribute("This method is intended for use in generated code only.",

tests/FSharp.Data.DesignTime.Tests/expected/Yaml,SimpleYaml.yaml,False,Root,,True,False,BackwardCompatible.expected

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ class YamlProvider+Root : FDR.BaseTypes.IJsonDocument
8989

9090

9191
class YamlProvider+Address : FDR.BaseTypes.IJsonDocument
92-
new : street:string -> city:string -> zip:int -> YamlProvider+Address
92+
new : street:string -> city:string -> zip:string -> YamlProvider+Address
9393
JsonRuntime.CreateRecord([| ("street",
9494
(street :> obj))
9595
("city",
@@ -114,11 +114,11 @@ class YamlProvider+Address : FDR.BaseTypes.IJsonDocument
114114
member WithStreet: street:string -> YamlProvider+Address
115115
JsonRuntime.WithRecordProperty(this, "street", (street :> obj), "")
116116

117-
member WithZip: zip:int -> YamlProvider+Address
117+
member WithZip: zip:string -> YamlProvider+Address
118118
JsonRuntime.WithRecordProperty(this, "zip", (zip :> obj), "")
119119

120-
member Zip: int with get
120+
member Zip: string with get
121121
let value = JsonRuntime.TryGetPropertyUnpackedWithPath(this, "zip")
122-
JsonRuntime.GetNonOptionalValue(value.Path, JsonRuntime.ConvertInteger("", value.JsonOpt), value.JsonOpt)
122+
JsonRuntime.GetNonOptionalValue(value.Path, JsonRuntime.ConvertString("", value.JsonOpt), value.JsonOpt)
123123

124124

0 commit comments

Comments
 (0)