Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/FSharp.Data.DesignTime/CommonProviderImplementation/Helpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,17 @@ module internal ProviderHelpers =
member x.Inverse(denominator) : Type =
ProvidedMeasureBuilder.Inverse(denominator) }

#if NET6_0_OR_GREATER
/// Returns true when the target runtime assembly is a .NET 6+ build and therefore
/// supports System.DateOnly / System.TimeOnly in generated types.
let runtimeSupportsNet6Types (runtimeAssemblyPath: string) =
// The assembly path contains the TFM, e.g. "…/net8.0/FSharp.Data.dll".
// Anything matching "/net<N>." where N ≥ 6 is a net6+ target.
let path = runtimeAssemblyPath.Replace('\\', '/').ToLowerInvariant()
let m = System.Text.RegularExpressions.Regex.Match(path, @"/net(\d+)\.")
m.Success && (int m.Groups.[1].Value) >= 6
#endif

let asyncMap (resultType: Type) (valueAsync: Expr<Async<'T>>) (body: Expr<'T> -> Expr) =
let (?) = QuotationBuilder.(?)
let convFunc = ReflectionHelpers.makeDelegate (Expr.Cast >> body) typeof<'T>
Expand Down
27 changes: 17 additions & 10 deletions src/FSharp.Data.DesignTime/Csv/CsvProvider.fs
Original file line number Diff line number Diff line change
Expand Up @@ -103,16 +103,23 @@ type public CsvProvider(cfg: TypeProviderConfig) as this =
let inferredFields =
use _holder = IO.logTime "Inference" sample

sampleCsv.InferColumnTypes(
inferRows,
TextRuntime.GetMissingValues missingValuesStr,
inferenceMode,
TextRuntime.GetCulture cultureStr,
schema,
assumeMissingValues,
preferOptionals,
unitsOfMeasureProvider
)
let fields =
sampleCsv.InferColumnTypes(
inferRows,
TextRuntime.GetMissingValues missingValuesStr,
inferenceMode,
TextRuntime.GetCulture cultureStr,
schema,
assumeMissingValues,
preferOptionals,
unitsOfMeasureProvider
)
#if NET6_0_OR_GREATER
if ProviderHelpers.runtimeSupportsNet6Types cfg.RuntimeAssembly then fields
else fields |> List.map StructuralInference.downgradeNet6PrimitiveProperty
#else
fields
#endif

use _holder = IO.logTime "TypeGeneration" sample

Expand Down
42 changes: 34 additions & 8 deletions src/FSharp.Data.DesignTime/Html/HtmlGenerator.fs
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,11 @@ module internal HtmlGenerator =
let private createTableType
getTableTypeName
(inferenceParameters, missingValuesStr, cultureStr)
supportsNet6Types
(table: HtmlTable)
=

let columns =
let rawColumns =
match table.InferedProperties with
| Some inferedProperties -> inferedProperties
| None ->
Expand All @@ -52,6 +53,14 @@ module internal HtmlGenerator =
else
table.Rows)

let columns =
#if NET6_0_OR_GREATER
if supportsNet6Types then rawColumns
else rawColumns |> List.map StructuralInference.downgradeNet6PrimitiveProperty
#else
rawColumns
#endif

let fields =
columns
|> List.mapi (fun index field ->
Expand Down Expand Up @@ -128,9 +137,17 @@ module internal HtmlGenerator =

create, tableType

let private createListType getListTypeName (inferenceParameters, missingValuesStr, cultureStr) (list: HtmlList) =
let private createListType getListTypeName (inferenceParameters, missingValuesStr, cultureStr) supportsNet6Types (list: HtmlList) =

let columns = HtmlInference.inferListType inferenceParameters list.Values
let rawColumns = HtmlInference.inferListType inferenceParameters list.Values

let columns =
#if NET6_0_OR_GREATER
if supportsNet6Types then rawColumns
else StructuralInference.downgradeNet6Types rawColumns
#else
rawColumns
#endif

let listItemType, conv =
match columns with
Expand Down Expand Up @@ -185,14 +202,23 @@ module internal HtmlGenerator =
let private createDefinitionListType
getDefinitionListTypeName
(inferenceParameters, missingValuesStr, cultureStr)
supportsNet6Types
(definitionList: HtmlDefinitionList)
=

let getListTypeName = typeNameGenerator ()

let createListType index (list: HtmlList) =

let columns = HtmlInference.inferListType inferenceParameters list.Values
let rawColumns = HtmlInference.inferListType inferenceParameters list.Values

let columns =
#if NET6_0_OR_GREATER
if supportsNet6Types then rawColumns
else StructuralInference.downgradeNet6Types rawColumns
#else
rawColumns
#endif

let listItemType, conv =
match columns with
Expand Down Expand Up @@ -264,7 +290,7 @@ module internal HtmlGenerator =

definitionListType

let generateTypes asm ns typeName parameters htmlObjects =
let generateTypes asm ns typeName parameters supportsNet6Types htmlObjects =

let htmlType =
ProvidedTypeDefinition(
Expand Down Expand Up @@ -303,7 +329,7 @@ module internal HtmlGenerator =
match htmlObj with
| Table table ->
let containerType = getOrCreateContainer "Tables"
let create, tableType = createTableType getTypeName parameters table
let create, tableType = createTableType getTypeName parameters supportsNet6Types table
htmlType.AddMember tableType

containerType.AddMember
Expand All @@ -315,7 +341,7 @@ module internal HtmlGenerator =

| List list ->
let containerType = getOrCreateContainer "Lists"
let create, tableType = createListType getTypeName parameters list
let create, tableType = createListType getTypeName parameters supportsNet6Types list
htmlType.AddMember tableType

containerType.AddMember
Expand All @@ -326,7 +352,7 @@ module internal HtmlGenerator =
)
| DefinitionList definitionList ->
let containerType = getOrCreateContainer "DefinitionLists"
let tableType = createDefinitionListType getTypeName parameters definitionList
let tableType = createDefinitionListType getTypeName parameters supportsNet6Types definitionList
htmlType.AddMember tableType

containerType.AddMember
Expand Down
8 changes: 7 additions & 1 deletion src/FSharp.Data.DesignTime/Html/HtmlProvider.fs
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,15 @@ type public HtmlProvider(cfg: TypeProviderConfig) as this =
PreferOptionals = preferOptionals
InferenceMode = inferenceMode }

#if NET6_0_OR_GREATER
let supportsNet6Types = ProviderHelpers.runtimeSupportsNet6Types cfg.RuntimeAssembly
#else
let supportsNet6Types = false
#endif

doc
|> HtmlRuntime.getHtmlObjects (Some inferenceParameters) includeLayoutTables
|> HtmlGenerator.generateTypes asm ns typeName (inferenceParameters, missingValuesStr, cultureStr)
|> HtmlGenerator.generateTypes asm ns typeName (inferenceParameters, missingValuesStr, cultureStr) supportsNet6Types

use _holder = IO.logTime "TypeGeneration" sample

Expand Down
51 changes: 29 additions & 22 deletions src/FSharp.Data.DesignTime/Json/JsonProvider.fs
Original file line number Diff line number Diff line change
Expand Up @@ -76,28 +76,35 @@ type public JsonProvider(cfg: TypeProviderConfig) as this =
let inferedType =
use _holder = IO.logTime "Inference" (if schema <> "" then schema else sample)

if schema <> "" then
// Use the JSON Schema for type inference
use _holder = IO.logTime "SchemaInference" schema

let schemaValue = JsonValue.Parse(value)
let jsonSchema = JsonSchema.parseSchema schemaValue
JsonSchema.schemaToInferedType unitsOfMeasureProvider jsonSchema
else
// Use sample-based inference
let samples =
use _holder = IO.logTime "Parsing" sample

if sampleIsList then
JsonDocument.CreateList(new StringReader(value))
|> Array.map (fun doc -> doc.JsonValue)
else
[| JsonValue.Parse(value) |]

samples
|> Array.map (fun sampleJson ->
JsonInference.inferType unitsOfMeasureProvider inferenceMode cultureInfo "" sampleJson)
|> Array.fold (StructuralInference.subtypeInfered false) InferedType.Top
let rawInfered =
if schema <> "" then
// Use the JSON Schema for type inference
use _holder = IO.logTime "SchemaInference" schema

let schemaValue = JsonValue.Parse(value)
let jsonSchema = JsonSchema.parseSchema schemaValue
JsonSchema.schemaToInferedType unitsOfMeasureProvider jsonSchema
else
// Use sample-based inference
let samples =
use _holder = IO.logTime "Parsing" sample

if sampleIsList then
JsonDocument.CreateList(new StringReader(value))
|> Array.map (fun doc -> doc.JsonValue)
else
[| JsonValue.Parse(value) |]

samples
|> Array.map (fun sampleJson ->
JsonInference.inferType unitsOfMeasureProvider inferenceMode cultureInfo "" sampleJson)
|> Array.fold (StructuralInference.subtypeInfered false) InferedType.Top
#if NET6_0_OR_GREATER
if ProviderHelpers.runtimeSupportsNet6Types cfg.RuntimeAssembly then rawInfered
else StructuralInference.downgradeNet6Types rawInfered
#else
rawInfered
#endif

use _holder = IO.logTime "TypeGeneration" (if schema <> "" then schema else sample)

Expand Down
31 changes: 22 additions & 9 deletions src/FSharp.Data.DesignTime/Xml/XmlProvider.fs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,13 @@ type public XmlProvider(cfg: TypeProviderConfig) as this =
let inferedType =
use _holder = IO.logTime "Inference" sample

schemaSet |> XsdParsing.getElements |> List.ofSeq |> XsdInference.inferElements
let t = schemaSet |> XsdParsing.getElements |> List.ofSeq |> XsdInference.inferElements
#if NET6_0_OR_GREATER
if ProviderHelpers.runtimeSupportsNet6Types cfg.RuntimeAssembly then t
else StructuralInference.downgradeNet6Types t
#else
t
#endif

use _holder = IO.logTime "TypeGeneration" sample

Expand Down Expand Up @@ -113,14 +119,21 @@ type public XmlProvider(cfg: TypeProviderConfig) as this =
let inferedType =
use _holder = IO.logTime "Inference" sample

samples
|> XmlInference.inferType
unitsOfMeasureProvider
inferenceMode
(TextRuntime.GetCulture cultureStr)
false
globalInference
|> Array.fold (StructuralInference.subtypeInfered false) InferedType.Top
let t =
samples
|> XmlInference.inferType
unitsOfMeasureProvider
inferenceMode
(TextRuntime.GetCulture cultureStr)
false
globalInference
|> Array.fold (StructuralInference.subtypeInfered false) InferedType.Top
#if NET6_0_OR_GREATER
if ProviderHelpers.runtimeSupportsNet6Types cfg.RuntimeAssembly then t
else StructuralInference.downgradeNet6Types t
#else
t
#endif

use _holder = IO.logTime "TypeGeneration" sample

Expand Down
60 changes: 60 additions & 0 deletions src/FSharp.Data.Runtime.Utilities/StructuralInference.fs
Original file line number Diff line number Diff line change
Expand Up @@ -623,3 +623,63 @@ let inferPrimitiveType
[<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.")>]
let getInferedTypeFromString unitsOfMeasureProvider inferenceMode cultureInfo value unit =
inferPrimitiveType unitsOfMeasureProvider inferenceMode cultureInfo value unit

#if NET6_0_OR_GREATER
/// Replaces DateOnly → DateTime and TimeOnly → TimeSpan throughout an InferedType tree.
/// Used in design-time code when the target framework does not support these .NET 6+ types.
let internal downgradeNet6Types (inferedType: InferedType) : InferedType =
let downgradeTag tag =
match tag with
| InferedTypeTag.DateOnly -> InferedTypeTag.DateTime
| InferedTypeTag.TimeOnly -> InferedTypeTag.TimeSpan
| _ -> tag

let downgradeType (typ: Type) =
if typ = typeof<DateOnly> then typeof<DateTime>
elif typ = typeof<TimeOnly> then typeof<TimeSpan>
else typ

// Use reference-equality-based visited set to handle cyclic InferedType graphs
// (e.g. recursive XML schemas). When a cycle is detected we return the original node.
let visited =
System.Collections.Generic.HashSet<InferedType>(
{ new System.Collections.Generic.IEqualityComparer<InferedType> with
member _.Equals(x, y) = obj.ReferenceEquals(x, y)
member _.GetHashCode(x) = System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(x) })

let rec convert infType =
if not (visited.Add(infType)) then
infType // cycle detected – return original to avoid infinite recursion
else
let result =
match infType with
| InferedType.Primitive(typ, unit, optional, overrideOnMerge) ->
InferedType.Primitive(downgradeType typ, unit, optional, overrideOnMerge)
| InferedType.Record(name, props, optional) ->
InferedType.Record(name, props |> List.map (fun p -> { p with Type = convert p.Type }), optional)
| InferedType.Collection(order, types) ->
InferedType.Collection(
order |> List.map downgradeTag,
types |> Map.toSeq |> Seq.map (fun (k, (m, t)) -> downgradeTag k, (m, convert t)) |> Map.ofSeq)
| InferedType.Heterogeneous(types, containsOptional) ->
InferedType.Heterogeneous(
types |> Map.toSeq |> Seq.map (fun (k, t) -> downgradeTag k, convert t) |> Map.ofSeq,
containsOptional)
| InferedType.Json(innerType, optional) -> InferedType.Json(convert innerType, optional)
| _ -> infType
result

convert inferedType

/// Replaces DateOnly → DateTime and TimeOnly → TimeSpan in a PrimitiveInferedProperty.
/// Used in design-time code when the target framework does not support these .NET 6+ types.
let internal downgradeNet6PrimitiveProperty (field: StructuralTypes.PrimitiveInferedProperty) =
let v = field.Value

if v.InferedType = typeof<DateOnly> then
{ field with Value = { v with InferedType = typeof<DateTime>; RuntimeType = typeof<DateTime> } }
elif v.InferedType = typeof<TimeOnly> then
{ field with Value = { v with InferedType = typeof<TimeSpan>; RuntimeType = typeof<TimeSpan> } }
else
field
#endif
2 changes: 1 addition & 1 deletion src/FSharp.Data.Xml.Core/FSharp.Data.Xml.Core.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
<OtherFlags>$(OtherFlags) --warnon:1182 --nowarn:10001 --nowarn:44</OtherFlags>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
Expand Down
4 changes: 4 additions & 0 deletions src/FSharp.Data.Xml.Core/XsdInference.fs
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,11 @@ module internal XsdInference =
function
| XmlTypeCode.Int -> typeof<int>
| XmlTypeCode.Long -> typeof<int64>
#if NET6_0_OR_GREATER
| XmlTypeCode.Date -> typeof<System.DateOnly>
#else
| XmlTypeCode.Date -> typeof<System.DateTime>
#endif
| XmlTypeCode.DateTime -> typeof<System.DateTimeOffset>
| XmlTypeCode.Boolean -> typeof<bool>
| XmlTypeCode.Decimal -> typeof<decimal>
Expand Down
Loading