From 0ffbae80ea1537fc000283ecdb96618c0e855648 Mon Sep 17 00:00:00 2001 From: Cody Tapscott <84105208+topolarity@users.noreply.github.com> Date: Thu, 25 Jul 2024 05:42:25 -0400 Subject: [PATCH] TOML: Make `Dates` a type parameter (#55017) This will allow us to resolve the `Dates` at compile-time eventually. It also fixes `TOML.Parser()` to return Dates types again. --- base/loading.jl | 10 +++++--- base/toml_parser.jl | 39 +++++++++++++++++-------------- stdlib/REPL/src/Pkg_beforeload.jl | 4 +++- stdlib/TOML/src/TOML.jl | 22 +++++++---------- stdlib/TOML/test/values.jl | 15 ++++++++++++ 5 files changed, 54 insertions(+), 36 deletions(-) diff --git a/base/loading.jl b/base/loading.jl index bd985e431e497..a768be286c83d 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -264,11 +264,15 @@ const LOADING_CACHE = Ref{Union{LoadingCache, Nothing}}(nothing) LoadingCache() = LoadingCache(load_path(), Dict(), Dict(), Dict(), Set(), Dict(), Dict(), Dict()) -struct TOMLCache - p::TOML.Parser +struct TOMLCache{Dates} + p::TOML.Parser{Dates} d::Dict{String, CachedTOMLDict} end -const TOML_CACHE = TOMLCache(TOML.Parser(), Dict{String, Dict{String, Any}}()) +TOMLCache(p::TOML.Parser) = TOMLCache(p, Dict{String, CachedTOMLDict}()) +# TODO: Delete this converting constructor once Pkg stops using it +TOMLCache(p::TOML.Parser, d::Dict{String, Dict{String, Any}}) = TOMLCache(p, convert(Dict{String, CachedTOMLDict}, d)) + +const TOML_CACHE = TOMLCache(TOML.Parser{nothing}()) parsed_toml(project_file::AbstractString) = parsed_toml(project_file, TOML_CACHE, require_lock) function parsed_toml(project_file::AbstractString, toml_cache::TOMLCache, toml_lock::ReentrantLock) diff --git a/base/toml_parser.jl b/base/toml_parser.jl index d50ca3b423c26..cc1455f61928b 100644 --- a/base/toml_parser.jl +++ b/base/toml_parser.jl @@ -38,7 +38,7 @@ const TOMLDict = Dict{String, Any} # Parser # ########## -mutable struct Parser +mutable struct Parser{Dates} str::String # 1 character look ahead current_char::Char @@ -86,12 +86,12 @@ mutable struct Parser filepath::Union{String, Nothing} # Optionally populate with the Dates stdlib to change the type of Date types returned - Dates::Union{Module, Nothing} + Dates::Union{Module, Nothing} # TODO: remove once Pkg is updated end -function Parser(str::String; filepath=nothing) +function Parser{Dates}(str::String; filepath=nothing) where {Dates} root = TOMLDict() - l = Parser( + l = Parser{Dates}( str, # str EOF_CHAR, # current_char firstindex(str), # pos @@ -112,6 +112,7 @@ function Parser(str::String; filepath=nothing) startup(l) return l end + function startup(l::Parser) # Populate our one character look-ahead c = eat_char(l) @@ -122,8 +123,10 @@ function startup(l::Parser) end end -Parser() = Parser("") -Parser(io::IO) = Parser(read(io, String)) +Parser{Dates}() where {Dates} = Parser{Dates}("") +Parser{Dates}(io::IO) where {Dates} = Parser{Dates}(read(io, String)) + +# Parser(...) will be defined by TOML stdlib function reinit!(p::Parser, str::String; filepath::Union{Nothing, String}=nothing) p.str = str @@ -1021,11 +1024,11 @@ function parse_datetime(l) return try_return_datetime(l, year, month, day, h, m, s, ms) end -function try_return_datetime(p, year, month, day, h, m, s, ms) - Dates = p.Dates - if Dates !== nothing +function try_return_datetime(p::Parser{Dates}, year, month, day, h, m, s, ms) where Dates + if Dates !== nothing || p.Dates !== nothing + mod = Dates !== nothing ? Dates : p.Dates try - return Dates.DateTime(year, month, day, h, m, s, ms) + return mod.DateTime(year, month, day, h, m, s, ms) catch ex ex isa ArgumentError && return ParserError(ErrParsingDateTime) rethrow() @@ -1035,11 +1038,11 @@ function try_return_datetime(p, year, month, day, h, m, s, ms) end end -function try_return_date(p, year, month, day) - Dates = p.Dates - if Dates !== nothing +function try_return_date(p::Parser{Dates}, year, month, day) where Dates + if Dates !== nothing || p.Dates !== nothing + mod = Dates !== nothing ? Dates : p.Dates try - return Dates.Date(year, month, day) + return mod.Date(year, month, day) catch ex ex isa ArgumentError && return ParserError(ErrParsingDateTime) rethrow() @@ -1058,11 +1061,11 @@ function parse_local_time(l::Parser) return try_return_time(l, h, m, s, ms) end -function try_return_time(p, h, m, s, ms) - Dates = p.Dates - if Dates !== nothing +function try_return_time(p::Parser{Dates}, h, m, s, ms) where Dates + if Dates !== nothing || p.Dates !== nothing + mod = Dates !== nothing ? Dates : p.Dates try - return Dates.Time(h, m, s, ms) + return mod.Time(h, m, s, ms) catch ex ex isa ArgumentError && return ParserError(ErrParsingDateTime) rethrow() diff --git a/stdlib/REPL/src/Pkg_beforeload.jl b/stdlib/REPL/src/Pkg_beforeload.jl index 2051e11711b0f..ebd0cd255ce19 100644 --- a/stdlib/REPL/src/Pkg_beforeload.jl +++ b/stdlib/REPL/src/Pkg_beforeload.jl @@ -71,7 +71,9 @@ end function projname(project_file::String) if isfile(project_file) name = try - p = Base.TOML.Parser() + # The `nothing` here means that this TOML parser does not return proper Dates.jl + # objects - but that's OK since we're just checking the name here. + p = Base.TOML.Parser{nothing}() Base.TOML.reinit!(p, read(project_file, String); filepath=project_file) proj = Base.TOML.parse(p) get(proj, "name", nothing) diff --git a/stdlib/TOML/src/TOML.jl b/stdlib/TOML/src/TOML.jl index 7414b5dc686f4..94d2808c0bc24 100644 --- a/stdlib/TOML/src/TOML.jl +++ b/stdlib/TOML/src/TOML.jl @@ -38,16 +38,10 @@ performance if a larger number of small files are parsed. """ const Parser = Internals.Parser -""" - DTParser() - -Constructor for a TOML `Parser` which returns date and time objects from Dates. -""" -function DTParser(args...; kwargs...) - parser = Parser(args...; kwargs...) - parser.Dates = Dates - return parser -end +# Dates-enabled constructors +Parser() = Parser{Dates}() +Parser(io::IO) = Parser{Dates}(io) +Parser(str::String; filepath=nothing) = Parser{Dates}(str; filepath) """ parsefile(f::AbstractString) @@ -59,7 +53,7 @@ Parse file `f` and return the resulting table (dictionary). Throw a See also [`TOML.tryparsefile`](@ref). """ parsefile(f::AbstractString) = - Internals.parse(DTParser(readstring(f); filepath=abspath(f))) + Internals.parse(Parser(readstring(f); filepath=abspath(f))) parsefile(p::Parser, f::AbstractString) = Internals.parse(Internals.reinit!(p, readstring(f); filepath=abspath(f))) @@ -73,7 +67,7 @@ Parse file `f` and return the resulting table (dictionary). Return a See also [`TOML.parsefile`](@ref). """ tryparsefile(f::AbstractString) = - Internals.tryparse(DTParser(readstring(f); filepath=abspath(f))) + Internals.tryparse(Parser(readstring(f); filepath=abspath(f))) tryparsefile(p::Parser, f::AbstractString) = Internals.tryparse(Internals.reinit!(p, readstring(f); filepath=abspath(f))) @@ -87,7 +81,7 @@ Throw a [`ParserError`](@ref) upon failure. See also [`TOML.tryparse`](@ref). """ parse(str::AbstractString) = - Internals.parse(DTParser(String(str))) + Internals.parse(Parser(String(str))) parse(p::Parser, str::AbstractString) = Internals.parse(Internals.reinit!(p, String(str))) parse(io::IO) = parse(read(io, String)) @@ -103,7 +97,7 @@ Return a [`ParserError`](@ref) upon failure. See also [`TOML.parse`](@ref). """ tryparse(str::AbstractString) = - Internals.tryparse(DTParser(String(str))) + Internals.tryparse(Parser(String(str))) tryparse(p::Parser, str::AbstractString) = Internals.tryparse(Internals.reinit!(p, String(str))) tryparse(io::IO) = tryparse(read(io, String)) diff --git a/stdlib/TOML/test/values.jl b/stdlib/TOML/test/values.jl index be2ed3acce5b5..4fc49d47fc98d 100644 --- a/stdlib/TOML/test/values.jl +++ b/stdlib/TOML/test/values.jl @@ -4,16 +4,31 @@ using Test using TOML using TOML: Internals +# Construct an explicit Parser to test the "cached" version of parsing +const test_parser = TOML.Parser() + function testval(s, v) f = "foo = $s" + # First, test with the standard entrypoint parsed = TOML.parse(f)["foo"] return isequal(v, parsed) && typeof(v) == typeof(parsed) + (!isequal(v, parsed) || typeof(v) != typeof(parsed)) && return false + # Next, test with the "cached" (explicit Parser) entrypoint + parsed = TOML.parse(test_parser, f)["foo"] + (!isequal(v, parsed) || typeof(v) != typeof(parsed)) && return false + return true end function failval(s, v) f = "foo = $s" + # First, test with the standard entrypoint err = TOML.tryparse(f); return err isa TOML.Internals.ParserError && err.type == v + (!isa(err, TOML.Internals.ParserError) || err.type != v) && return false + # Next, test with the "cached" (explicit Parser) entrypoint + err = TOML.tryparse(test_parser, f); + (!isa(err, TOML.Internals.ParserError) || err.type != v) && return false + return true end @testset "Numbers" begin