Skip to content

Commit

Permalink
Merge pull request #96 from MichaelHatherly/mh/fix-templates
Browse files Browse the repository at this point in the history
Move template expansion to "format-time"
  • Loading branch information
MichaelHatherly authored Aug 12, 2020
2 parents 1594674 + c016667 commit 339cd6d
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 49 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
docs/build
docs/site
docs/Manifest.toml
Manifest.toml
64 changes: 62 additions & 2 deletions src/abbreviations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -384,9 +384,9 @@ function format(::TypedMethodSignatures, buf, doc)
# ideally we would check that the method signature matches the Tuple{...} signature
# but that is not straightforward because of how expressive Julia can be
if Sys.iswindows()
t = tuples[findlast(t -> t isa DataType && string(t.name) == "Tuple" && length(t.types) == N, tuples)]
t = tuples[findlast(t -> t isa DataType && t <: Tuple && length(t.types) == N, tuples)]
else
t = tuples[findfirst(t -> t isa DataType && string(t.name) == "Tuple" && length(t.types) == N, tuples)]
t = tuples[findfirst(t -> t isa DataType && t <: Tuple && length(t.types) == N, tuples)]
end
printmethod(buf, binding, func, method, t)
println(buf)
Expand Down Expand Up @@ -611,3 +611,63 @@ of the docstring body that should be spliced into a template.
const DOCSTRING = DocStringTemplate()

# NOTE: no `format` needed for this 'mock' abbreviation.

is_docstr_template(::DocStringTemplate) = true
is_docstr_template(other) = false

"""
Internal abbreviation type used to wrap templated docstrings.
`Location` is a `Symbol`, either `:before` or `:after`. `dict` stores a
reference to a module's templates.
"""
struct Template{Location} <: Abbreviation
dict::Dict{Symbol,Vector{Any}}
end

function format(abbr::Template, buf, doc)
# Find the applicable template based on the kind of docstr.
parts = get_template(abbr.dict, template_key(doc))
# Replace the abbreviation with either the parts of the template found
# before the `DOCSTRING` abbreviation, or after it. When no `DOCSTRING`
# exists in the template, which shouldn't really happen then nothing will
# get included here.
for index in included_range(abbr, parts)
# We don't call `DocStringExtensions.format` here since we need to be
# able to format any content in docstrings, rather than just
# abbreviations.
Docs.formatdoc(buf, doc, parts[index])
end
end

function included_range(abbr::Template, parts::Vector)
# Select the correct indexing depending on what we find.
build_range(::Template, ::Nothing) = 0:-1
build_range(::Template{:before}, index) = 1:(index - 1)
build_range(::Template{:after}, index) = (index + 1):lastindex(parts)
# Search for index from either the front or back.
find_index(::Template{:before}) = findfirst(is_docstr_template, parts)
find_index(::Template{:after}) = findlast(is_docstr_template, parts)
# Find and return the correct indices.
return build_range(abbr, find_index(abbr))
end

function template_key(doc::Docs.DocStr)
# Local helper methods for extracting the template key from a docstring.
ismacro(b::Docs.Binding) = startswith(string(b.var), '@')
objname(obj::Union{Function,Module,DataType,UnionAll,Core.IntrinsicFunction}, b::Docs.Binding) = nameof(obj)
objname(obj, b::Docs.Binding) = Symbol("") # Empty to force resolving to `:CONSTANTS` below.
# Select the key returned based on input argument types.
_key(::Module, sig, binding) = :MODULES
_key(::Function, ::typeof(Union{}), binding) = ismacro(binding) ? :MACROS : :FUNCTIONS
_key(::Function, sig, binding) = ismacro(binding) ? :MACROS : :METHODS
_key(::DataType, ::typeof(Union{}), binding) = :TYPES
_key(::DataType, sig, binding) = :METHODS
_key(other, sig, binding) = :DEFAULT

binding = doc.data[:binding]
obj = Docs.resolve(binding)
name = objname(obj, binding)
key = name === binding.var ? _key(obj, doc.data[:typesig], binding) : :CONSTANTS
return key
end
50 changes: 14 additions & 36 deletions src/templates.jl
Original file line number Diff line number Diff line change
Expand Up @@ -102,18 +102,21 @@ end
# On v0.6 and below it seems it was assumed to be (docstr::String, expr::Expr), but on v0.7
# it is (source::LineNumberNode, mod::Module, docstr::String, expr::Expr)
function template_hook(source::LineNumberNode, mod::Module, docstr, expr::Expr)
local docex = interp_string(docstr)
if isdefined(mod, TEMP_SYM) && Meta.isexpr(docex, :string)
local templates = getfield(mod, TEMP_SYM)
local template = get_template(templates, expression_type(expr))
local out = Expr(:string)
for t in template
t == DOCSTRING ? append!(out.args, docex.args) : push!(out.args, t)
end
return (source, mod, out, expr)
else
return (source, mod, docstr, expr)
# During macro expansion we only need to wrap docstrings in special
# abbreviations that later print out what was before and after the
# docstring in it's specific template. This is only done when the module
# actually defines templates.
if isdefined(mod, TEMP_SYM)
dict = getfield(mod, TEMP_SYM)
# We unwrap interpolated strings so that we can add the `:before` and
# `:after` abbreviations. Otherwise they're just left as is.
unwrapped = Meta.isexpr(docstr, :string) ? docstr.args : [docstr]
before, after = Template{:before}(dict), Template{:after}(dict)
# Rebuild the original docstring, but with the template abbreviations
# surrounding it.
docstr = Expr(:string, before, unwrapped..., after)
end
return (source, mod, docstr, expr)
end

function template_hook(docstr, expr::Expr)
Expand All @@ -123,29 +126,4 @@ end

template_hook(args...) = args

interp_string(str::AbstractString) = Expr(:string, str)
interp_string(other) = other

get_template(t::Dict, k::Symbol) = haskey(t, k) ? t[k] : get(t, :DEFAULT, Any[DOCSTRING])

function expression_type(ex::Expr)
# Expression heads changed in JuliaLang/julia/pull/23157 to match the new keyword syntax.
if VERSION < v"0.7.0-DEV.1263" && Meta.isexpr(ex, [:type, :bitstype])
:TYPES
elseif Meta.isexpr(ex, :module)
:MODULES
elseif Meta.isexpr(ex, [:struct, :abstract, :typealias, :primitive])
:TYPES
elseif Meta.isexpr(ex, :macro)
:MACROS
elseif Meta.isexpr(ex, [:function, :(=)]) && Meta.isexpr(ex.args[1], :call) || (Meta.isexpr(ex.args[1], :where) && Meta.isexpr(ex.args[1].args[1], :call))
:METHODS
elseif Meta.isexpr(ex, :function)
:FUNCTIONS
elseif Meta.isexpr(ex, [:const, :(=)])
:CONSTANTS
else
:DEFAULT
end
end
expression_type(other) = :DEFAULT
4 changes: 2 additions & 2 deletions test/TestModule/M.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ g(x = 1, y = 2, z = 3; kwargs...) = x

h(x::Int, y::Int = 2, z::Int = 3; kwargs...) = x

const A{T} = Union{Vector{T}, Matrix{T}}
const A{T} = Union{Array{T, 3}, Array{T, 4}}

h_1(x::A) = x
h_2(x::A{Int}) = x
Expand All @@ -29,7 +29,7 @@ k_3(x, y::T, z::U) where {T, U} = x + y + z
k_4(::String, ::Int = 0) = nothing
k_5(::Type{T}, x::String, func::Union{Nothing, Function} = nothing) where T <: Number = x
k_6(x::Vector{T}) where T <: Number = x
k_7(x::Union{T,Nothing}, y::T = zero(T)) where {T <: Number} = x
k_7(x::Union{T,Nothing}, y::T = zero(T)) where {T <: Integer} = x
k_8(x) = x
k_9(x::T where T<:Any) = x
k_10(x::T) where T = x
Expand Down
17 changes: 15 additions & 2 deletions test/templates.jl
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,18 @@ const K = 1
"mutable struct `T`"
mutable struct T end

"`@kwdef` struct `S`"
Base.@kwdef struct S end

"method `f`"
f(x) = x

"method `g`"
g(::Type{T}) where {T} = T # Issue 32

"inlined method `h`"
@inline h(x) = x

"macro `@m`"
macro m(x) end

Expand All @@ -66,8 +72,15 @@ module InnerModule
"constant `K`"
const K = 1

"mutable struct `T`"
mutable struct T end
"""
mutable struct `T`
$(FIELDS)
"""
mutable struct T
"field docs for x"
x
end

"method `f`"
f(x) = x
Expand Down
17 changes: 10 additions & 7 deletions test/tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,9 @@ end
str = String(take!(buf))
@test occursin("\n```julia\n", str)
if Sys.iswindows()
@test occursin("h_1(x::Union{Array{T,2}, Array{T,1}} where T) -> Union{Array{T,2}, Array{T,1}} where T", str)
@test occursin("h_1(x::Union{Array{T,4}, Array{T,3}} where T) -> Union{Array{T,4}, Array{T,3}} where T", str)
else
@test occursin("h_1(x::Union{Array{T,1}, Array{T,2}} where T) -> Union{Array{T,1}, Array{T,2}} where T", str)
@test occursin("h_1(x::Union{Array{T,3}, Array{T,4}} where T) -> Union{Array{T,3}, Array{T,4}} where T", str)
end
@test occursin("\n```\n", str)

Expand Down Expand Up @@ -316,14 +316,14 @@ end

doc.data = Dict(
:binding => Docs.Binding(M, :k_7),
:typesig => Union{Tuple{Union{T, Nothing}}, Tuple{Union{T, Nothing}, T}, Tuple{T}} where T <: Number,
:typesig => Union{Tuple{Union{T, Nothing}}, Tuple{Union{T, Nothing}, T}, Tuple{T}} where T <: Integer,
:module => M,
)
DSE.format(DSE.TYPEDSIGNATURES, buf, doc)
str = String(take!(buf))
@test occursin("\n```julia\n", str)
@test occursin("\nk_7(x::Union{Nothing, T<:Number}) -> Union{Nothing, Number}\n", str)
@test occursin("\nk_7(x::Union{Nothing, T<:Number}, y::T<:Number) -> Union{Nothing, T<:Number}\n", str)
@test occursin("\nk_7(x::Union{Nothing, T<:Integer}) -> Union{Nothing, Integer}\n", str)
@test occursin("\nk_7(x::Union{Nothing, T<:Integer}, y::T<:Integer) -> Union{Nothing, T<:Integer}\n", str)
@test occursin("\n```\n", str)

doc.data = Dict(
Expand Down Expand Up @@ -423,12 +423,15 @@ end
let fmt = expr -> Markdown.plain(eval(:(@doc $expr)))
@test occursin("(DEFAULT)", fmt(:(TemplateTests.K)))
@test occursin("(TYPES)", fmt(:(TemplateTests.T)))
@test occursin("(TYPES)", fmt(:(TemplateTests.S)))
@test occursin("(METHODS, MACROS)", fmt(:(TemplateTests.f)))
@test occursin("(METHODS, MACROS)", fmt(:(TemplateTests.g)))
@test occursin("(METHODS, MACROS)", fmt(:(TemplateTests.h)))
@test occursin("(METHODS, MACROS)", fmt(:(TemplateTests.@m)))

@test occursin("(DEFAULT)", fmt(:(TemplateTests.InnerModule.K)))
@test occursin("(DEFAULT)", fmt(:(TemplateTests.InnerModule.T)))
@test occursin("field docs for x", fmt(:(TemplateTests.InnerModule.T)))
@test occursin("(METHODS, MACROS)", fmt(:(TemplateTests.InnerModule.f)))
@test occursin("(MACROS)", fmt(:(TemplateTests.InnerModule.@m)))

Expand Down Expand Up @@ -550,8 +553,8 @@ end
@test length(DSE.getmethods(M.f, Tuple{})) == 0
@test length(DSE.getmethods(M.f, Union{Tuple{}, Tuple{Any}})) == 1
@test length(DSE.getmethods(M.h_3, Tuple{M.A{Int}})) == 1
@test length(DSE.getmethods(M.h_3, Tuple{Vector{Int}})) == 1
@test length(DSE.getmethods(M.h_3, Tuple{Array{Int, 3}})) == 0
@test length(DSE.getmethods(M.h_3, Tuple{Array{Int, 3}})) == 1
@test length(DSE.getmethods(M.h_3, Tuple{Array{Int, 1}})) == 0
end
@testset "methodgroups" begin
@test length(DSE.methodgroups(M.f, Tuple{Any}, M)) == 1
Expand Down

0 comments on commit 339cd6d

Please sign in to comment.