-
Notifications
You must be signed in to change notification settings - Fork 284
Expand file tree
/
Copy pathStructuralTypes.fs
More file actions
272 lines (238 loc) · 11.2 KB
/
StructuralTypes.fs
File metadata and controls
272 lines (238 loc) · 11.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
namespace rec FSharp.Data.Runtime.StructuralTypes
open System
open FSharp.Data.Runtime
// --------------------------------------------------------------------------------------
// Types that represent the result of the type inference
// --------------------------------------------------------------------------------------
/// <summary>A property of a record has a name and type and may be optional</summary>
/// <namespacedoc>
/// <summary>Types that represent the result of static type inference.</summary>
/// </namespacedoc>
[<Obsolete("This API will be made internal in a future release. Please file an issue at https://github.com/fsprojects/FSharp.Data/issues/1458 if you need this public.")>]
type InferedProperty =
{ Name: string
mutable Type: InferedType }
override x.ToString() = sprintf "%A" x
/// For heterogeneous types (types that have multiple possible forms
/// such as differently named XML nodes or records and arrays mixed together)
/// this type represents the number of occurrences of individual forms
[<Struct;
Obsolete("This API will be made internal in a future release. Please file an issue at https://github.com/fsprojects/FSharp.Data/issues/1458 if you need this public.")>]
type InferedMultiplicity =
| Single
| OptionalSingle
| Multiple
/// For heterogeneous types, this represents the tag that defines the form
/// (that is either primitive type, collection, named record etc.)
[<RequireQualifiedAccess>]
[<Obsolete("This API will be made internal in a future release. Please file an issue at https://github.com/fsprojects/FSharp.Data/issues/1458 if you need this public.")>]
type InferedTypeTag =
// Unknown type
| Null
// Primitive types
| Number
| Boolean
| String
/// Allow for support of embedded json in e.g. xml documents
| Json
| DateTime
| TimeSpan
| DateTimeOffset
| Guid
#if NET6_0_OR_GREATER
| DateOnly
| TimeOnly
#endif
// Collections and sum types
| Collection
| Heterogeneous
// Possibly named record
| Record of string option
/// Represents inferred structural type. A type may be either primitive type
/// (one of those listed by `primitiveTypes`) or it can be collection,
/// (named) record and heterogeneous type. We also have `Null` type (which is
/// a subtype of all non-primitive types) and universal `Top` type.
///
/// * For collection, we infer the types of different things that appear in
/// the collection and how many times they do.
///
/// * A heterogeneous type (sum type) is simply a choice containing one
/// of multiple different possibilities
///
/// Why is collection not simply a list of Heterogeneous types? If we used that
/// we would lose information about multiplicity and so we would not be able
/// to generate nicer types!
[<CustomEquality; NoComparison; RequireQualifiedAccess>]
[<Obsolete("This API will be made internal in a future release. Please file an issue at https://github.com/fsprojects/FSharp.Data/issues/1458 if you need this public.")>]
type InferedType =
| Primitive of typ: Type * unit: option<System.Type> * optional: bool * shouldOverrideOnMerge: bool
| Record of name: string option * fields: InferedProperty list * optional: bool
| Json of typ: InferedType * optional: bool
| Collection of order: InferedTypeTag list * types: Map<InferedTypeTag, InferedMultiplicity * InferedType>
| Heterogeneous of types: Map<InferedTypeTag, InferedType> * containsOptional: bool
| Null
| Top
member x.IsOptional =
match x with
| Primitive(optional = true)
| Record(optional = true)
| Json(optional = true) -> true
| _ -> false
static member CanHaveEmptyValues typ =
typ = typeof<string> || typ = typeof<float>
/// When allowEmptyValues is true, we allow "" and double.NaN, otherwise
/// we make the type optional and use None instead.
/// It's currently only true in CsvProvider when PreferOptionals is set to false
member x.EnsuresHandlesMissingValues allowEmptyValues =
match x with
| Null
| Heterogeneous(containsOptional = true)
| Primitive(optional = true)
| Record(optional = true)
| Json(optional = true) -> x
| Primitive(typ, _, false, _) when allowEmptyValues && InferedType.CanHaveEmptyValues typ -> x
| Heterogeneous(map, false) -> Heterogeneous(map, true)
| Primitive(typ, unit, false, overrideOnMerge) -> Primitive(typ, unit, true, overrideOnMerge)
| Record(name, props, false) -> Record(name, props, true)
| Json(typ, false) -> Json(typ, true)
| Collection(order, types) ->
let typesR =
types
|> Map.map (fun _ (mult, typ) -> (if mult = Single then OptionalSingle else mult), typ)
Collection(order, typesR)
| Top -> failwith "EnsuresHandlesMissingValues: unexpected InferedType.Top"
member x.GetDropOptionality() =
match x with
| Primitive(typ, unit, true, overrideOnMerge) -> Primitive(typ, unit, false, overrideOnMerge), true
| Record(name, props, true) -> Record(name, props, false), true
| Json(typ, true) -> Json(typ, false), true
| Heterogeneous(map, true) -> Heterogeneous(map, false), true
| _ -> x, false
member x.DropOptionality() = x.GetDropOptionality() |> fst
// We need to implement custom equality that returns 'true' when
// values reference the same object (to support recursive types)
override x.GetHashCode() = -1
override x.Equals(y: obj) =
if y :? InferedType then
match x, y :?> InferedType with
| a, b when Object.ReferenceEquals(a, b) -> true
| Primitive(t1, ot1, b1, x1), Primitive(t2, ot2, b2, x2) -> t1 = t2 && ot1 = ot2 && b1 = b2 && x1 = x2
| Record(s1, pl1, b1), Record(s2, pl2, b2) -> s1 = s2 && pl1 = pl2 && b1 = b2
| Json(t1, o1), Json(t2, o2) -> t1 = t2 && o1 = o2
| Collection(o1, t1), Collection(o2, t2) -> o1 = o2 && t1 = t2
| Heterogeneous(m1, o1), Heterogeneous(m2, o2) -> m1 = m2 && o1 = o2
| Null, Null
| Top, Top -> true
| _ -> false
else
false
override x.ToString() = sprintf "%A" x
// ------------------------------------------------------------------------------------------------
// Additional operations for working with the inferred representation
type internal InferedTypeTag with
member x.NiceName =
match x with
| Null -> failwith "Null nodes should be skipped"
| Number -> "Number"
| Boolean -> "Boolean"
| String -> "String"
| DateTime -> "DateTime"
| TimeSpan -> "TimeSpan"
| DateTimeOffset -> "DateTimeOffset"
| Guid -> "Guid"
| Collection -> "Array"
| Heterogeneous -> "Choice"
| Record None -> "Record"
| Record(Some name) -> NameUtils.nicePascalName name
| Json -> "Json"
#if NET6_0_OR_GREATER
| DateOnly -> "DateOnly"
| TimeOnly -> "TimeOnly"
#endif
/// Converts tag to string code that can be passed to generated code
member x.Code =
match x with
| Record(Some name) -> "Record@" + name
| _ -> x.NiceName
/// Parses code returned by 'Code' member (to be used in provided code)
static member ParseCode(str: string) =
match str with
| s when s.StartsWith("Record@", StringComparison.Ordinal) -> Record(Some(s.Substring("Record@".Length)))
| "Record" -> Record None
| "Json" -> Json
| "Number" -> Number
| "Boolean" -> Boolean
| "String" -> String
| "DateTime" -> DateTime
| "TimeSpan" -> TimeSpan
| "DateTimeOffset" -> DateTimeOffset
| "Guid" -> Guid
| "Array" -> Collection
| "Choice" -> Heterogeneous
#if NET6_0_OR_GREATER
| "DateOnly" -> DateOnly
| "TimeOnly" -> TimeOnly
#endif
| "Null" -> failwith "Null nodes should be skipped"
| _ -> failwith "Invalid InferredTypeTag code"
/// Dummy type to represent that only "0" was found.
/// Will be generated as 'int', unless it's converted to Bit.
[<Struct;
Obsolete("This API will be made internal in a future release. Please file an issue at https://github.com/fsprojects/FSharp.Data/issues/1458 if you need this public.")>]
type Bit0 = Bit0
/// Dummy type to represent that only "1" was found
/// Will be generated as 'int', unless it's converted to Bit
[<Struct;
Obsolete("This API will be made internal in a future release. Please file an issue at https://github.com/fsprojects/FSharp.Data/issues/1458 if you need this public.")>]
type Bit1 = Bit1
/// Dummy type to represent that only one of "0" and "1" were found
/// Will be generated as a 'bool', unless it's converted to another numerical type
[<Struct;
Obsolete("This API will be made internal in a future release. Please file an issue at https://github.com/fsprojects/FSharp.Data/issues/1458 if you need this public.")>]
type Bit = Bit
// ------------------------------------------------------------------------------------------------
/// Represents a transformation of a type
[<RequireQualifiedAccess>]
[<Obsolete("This API will be made internal in a future release. Please file an issue at https://github.com/fsprojects/FSharp.Data/issues/1458 if you need this public.")>]
type TypeWrapper =
/// No transformation will be made to the type
| None
/// The type T will be converter to type T option
| Option
/// The type T will be converter to type Nullable<T>
| Nullable
static member FromOption optional =
if optional then TypeWrapper.Option else TypeWrapper.None
/// Represents type information about a primitive value (used mainly in the CSV provider)
/// This type captures the type, unit of measure and handling of missing values (if we
/// infer that the value may be missing, we can generate option<T> or nullable<T>)
type internal PrimitiveInferedValue =
{ InferedType: Type
RuntimeType: Type
UnitOfMeasure: Type option
TypeWrapper: TypeWrapper }
static member Create(typ, typWrapper, unit) =
let runtimeTyp =
if typ = typeof<Bit> then
typeof<bool>
elif typ = typeof<Bit0> || typ = typeof<Bit1> then
typeof<int>
else
typ
{ InferedType = typ
RuntimeType = runtimeTyp
UnitOfMeasure = unit
TypeWrapper = typWrapper }
static member Create(typ, optional, unit) =
PrimitiveInferedValue.Create(typ, TypeWrapper.FromOption optional, unit)
/// Represents type information about a primitive property (used mainly in the CSV provider)
/// This type captures the type, unit of measure and handling of missing values (if we
/// infer that the value may be missing, we can generate option<T> or nullable<T>)
type internal PrimitiveInferedProperty =
{ Name: string
Value: PrimitiveInferedValue }
static member Create(name, typ, (typWrapper: TypeWrapper), unit) =
{ Name = name
Value = PrimitiveInferedValue.Create(typ, typWrapper, unit) }
static member Create(name, typ, optional, unit) =
PrimitiveInferedProperty.Create(name, typ, TypeWrapper.FromOption optional, unit)