diff --git a/Project.toml b/Project.toml index 7c32caf..9e1133e 100644 --- a/Project.toml +++ b/Project.toml @@ -13,8 +13,9 @@ Parsers = "2.5.1" julia = "1.6" [extras] -Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Test", "OrderedCollections"] +test = ["Test", "JET", "OrderedCollections"] diff --git a/src/JSONBase.jl b/src/JSONBase.jl index 239c0d3..5f3ad0f 100644 --- a/src/JSONBase.jl +++ b/src/JSONBase.jl @@ -27,6 +27,70 @@ include("binary.jl") include("materialize.jl") include("json.jl") +# HACK to avoid inference recursion limit and the de-optimization: +# This works since know the inference recursion will terminate due to the fact that this +# method is only called when materializing a struct with definite number of fields, i.e. +# that is not self-referencing, so it is guaranteed that there are no cycles in a recursive +# `materialize` call. Especially, the `fieldcount` call in the struct fallback case within +# the `materialize` should have errored for this case. +# TODO we should revisit this hack when we start to support https://github.com/quinnj/JSONBase.jl/issues/3 +function validate_recursion_relation_sig(f, nargs::Int, sig) + @nospecialize f sig + sig = Base.unwrap_unionall(sig) + @assert sig isa DataType "unexpected `recursion_relation` call" + @assert sig.name === Tuple.name "unexpected `recursion_relation` call" + @assert length(sig.parameters) == nargs "unexpected `recursion_relation` call" + @assert sig.parameters[1] == typeof(f) "unexpected `recursion_relation` call" + return sig +end +@static if hasfield(Method, :recursion_relation) + let applyobject_recursion_relation = function ( + method::Method, topmost::Union{Nothing,Method}, + @nospecialize(sig), @nospecialize(topmostsig)) + # Core.println("applyobject") + # Core.println(" method = ", method) + # Core.println(" topmost = ", topmost) + # Core.println(" sig = ", sig) + # Core.println(" topmostsig = ", topmostsig) + sig = validate_recursion_relation_sig(applyobject, 3, sig) + topmostsig = validate_recursion_relation_sig(applyobject, 3, topmostsig) + return sig.parameters[2] ≠ topmostsig.parameters[2] + end + method = only(methods(applyobject, (Any,LazyValues,))) + method.recursion_relation = applyobject_recursion_relation + end + let applyfield_recursion_relation = function ( + method::Method, topmost::Union{Nothing,Method}, + @nospecialize(sig), @nospecialize(topmostsig)) + # Core.println("applyfield") + # Core.println(" method = ", method) + # Core.println(" topmost = ", topmost) + # Core.println(" sig = ", sig) + # Core.println(" topmostsig = ", topmostsig) + sig = validate_recursion_relation_sig(applyfield, 6, sig) + topmostsig = validate_recursion_relation_sig(applyfield, 6, topmostsig) + return sig.parameters[2] ≠ topmostsig.parameters[2] + end + method = only(methods(applyfield, (Type,Type,Any,Any,Any))) + method.recursion_relation = applyfield_recursion_relation + end + let _materialize_recursion_relation = function ( + method::Method, topmost::Union{Nothing,Method}, + @nospecialize(sig), @nospecialize(topmostsig)) + # Core.println("_materialize") + # Core.println(" method = ", method) + # Core.println(" topmost = ", topmost) + # Core.println(" sig = ", sig) + # Core.println(" topmostsig = ", topmostsig) + sig = validate_recursion_relation_sig(_materialize, 5, sig) + topmostsig = validate_recursion_relation_sig(_materialize, 5, topmostsig) + return sig.parameters[4] ≠ topmostsig.parameters[4] + end + method = only(methods(_materialize, (Any,LazyValue,Type,Type))) + method.recursion_relation = _materialize_recursion_relation + end +end + # a helper higher-order function that converts an # API.applyeach function that operates potentially on a # PtrString to one that operates on a String @@ -84,4 +148,4 @@ end # module # package docs # topretty # allow materialize on any ObjectLike? i.e. Dicts? (would need applyobject on Dict) - # checkout JSON5, Amazon Ion? \ No newline at end of file + # checkout JSON5, Amazon Ion? diff --git a/src/lazy.jl b/src/lazy.jl index 2d8eb3e..808631b 100644 --- a/src/lazy.jl +++ b/src/lazy.jl @@ -250,7 +250,7 @@ _applyobject(f::F, x) where {F} = applyobject(f, x) # we're now positioned at the start of the value val = lazy(buf, pos, len, b, opts) ret = keyvalfunc(key, val) - # if ret is not an Continue, then we're + # if ret is not an Continue, then we're # short-circuiting parsing via e.g. selection syntax # so return immediately ret isa Continue || return ret diff --git a/test/optimization.jl b/test/optimization.jl new file mode 100644 index 0000000..81de451 --- /dev/null +++ b/test/optimization.jl @@ -0,0 +1,24 @@ +using JSONBase, JET + +struct OptimizationFailureChecker end +function JET.configured_reports(::OptimizationFailureChecker, reports::Vector{JET.InferenceErrorReport}) + return filter(reports) do @nospecialize report::JET.InferenceErrorReport + isa(report, JET.OptimizationFailureReport) + end +end + +# https://github.com/quinnj/JSONBase.jl/issues/2 +struct RecursiveInferenceTest1 + a::Int + b::Int +end +@test_opt annotate_types=true report_config=OptimizationFailureChecker() JSONBase.materialize("""{ "a": 1, "b": 2 }""", RecursiveInferenceTest1) + +struct RecursiveInferenceTest2Inner + b::Int +end +struct RecursiveInferenceTest2 + a::Int + b::RecursiveInferenceTest2Inner +end +@test_opt annotate_types=true report_config=OptimizationFailureChecker() JSONBase.materialize("""{ "a": 1, "b": { "b": 2 } }""", RecursiveInferenceTest2) diff --git a/test/runtests.jl b/test/runtests.jl index 501d83c..3d61734 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -105,7 +105,7 @@ struct File end ["Gilbert", "2013", 24, true] ["Alexa", "2013", 29, true] ["May", "2012B", 14, false] - ["Deloise", "2012A", 19, true] + ["Deloise", "2012A", 19, true] """; jsonlines=true, float64=true) == [["Name", "Session", "Score", "Completed"], ["Gilbert", "2013", 24.0, true], @@ -294,3 +294,6 @@ end include("struct.jl") include("json.jl") include("numbers.jl") +@static if VERSION ≥ v"1.8" + @testset "Optimization test with JET" include("optimization.jl") +end