From 18722b71fef301f655d07479803b35898b58acab Mon Sep 17 00:00:00 2001 From: Fredrik Ekre Date: Thu, 30 Jan 2020 14:06:08 +0100 Subject: [PATCH] Support REPL softscope for Julia 1.5. (#1232) Co-authored-by: Morten Piibeleht Co-authored-by: Fredrik Ekre --- CHANGELOG.md | 5 +++ docs/src/man/doctests.md | 6 ++++ docs/src/man/syntax.md | 6 ++++ src/DocTests.jl | 5 +++ src/Expanders.jl | 5 +++ test/doctests/doctests.jl | 17 +++++++++- test/doctests/src/hardscope.md | 51 +++++++++++++++++++++++++++++ test/doctests/src/softscope.md | 57 +++++++++++++++++++++++++++++++++ test/doctests/stdouts/41.stdout | 8 +++++ test/doctests/stdouts/42.stdout | 8 +++++ 10 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 test/doctests/src/hardscope.md create mode 100644 test/doctests/src/softscope.md create mode 100644 test/doctests/stdouts/41.stdout create mode 100644 test/doctests/stdouts/42.stdout diff --git a/CHANGELOG.md b/CHANGELOG.md index 5698df0537..bb65c3fd30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Documenter.jl changelog +## Version `v0.24.5` + +* ![Enhancement][badge-enhancement] ![Bugfix][badge-bugfix] Documenter now correctly emulates the "REPL softscope" (Julia 1.5) in REPL-style doctest blocks and `@repl` blocks. ([#1232][github-1232]) + ## Version `v0.24.4` * ![Enhancement][badge-enhancement] Change the inline code to less distracting black color in the HTML light theme. ([#1212][github-1212], [#1222][github-1222]) @@ -508,6 +512,7 @@ [github-1216]: https://github.com/JuliaDocs/Documenter.jl/pull/1216 [github-1222]: https://github.com/JuliaDocs/Documenter.jl/pull/1222 [github-1223]: https://github.com/JuliaDocs/Documenter.jl/pull/1223 +[github-1232]: https://github.com/JuliaDocs/Documenter.jl/pull/1232 [documenterlatex]: https://github.com/JuliaDocs/DocumenterLaTeX.jl [documentermarkdown]: https://github.com/JuliaDocs/DocumenterMarkdown.jl diff --git a/docs/src/man/doctests.md b/docs/src/man/doctests.md index 8b45c86367..75a96d4318 100644 --- a/docs/src/man/doctests.md +++ b/docs/src/man/doctests.md @@ -81,6 +81,12 @@ and will suppress the output, although the line is still evaluated. Note that not all features of the REPL are supported such as shell and help modes. +!!! note "Soft vs hard scope" + + Julia 1.5 changed the REPL to use the _soft scope_ when handling global variables in + `for` loops etc. When using Documenter with Julia 1.5 or above, Documenter uses the soft + scope in `@repl`-blocks and REPL-type doctests. + ## Exceptions Doctests can also test for thrown exceptions and their stacktraces. Comparing of the actual diff --git a/docs/src/man/syntax.md b/docs/src/man/syntax.md index 634d9653ad..98c82c6264 100644 --- a/docs/src/man/syntax.md +++ b/docs/src/man/syntax.md @@ -516,6 +516,12 @@ Named `@repl ` blocks behave in the same way as named `@example ` bl will be written to, and the paths in `include` calls are interpreted to be relative to `pwd`. This can be customized with the `workdir` keyword of [`makedocs`](@ref). +!!! note "Soft vs hard scope" + + Julia 1.5 changed the REPL to use the _soft scope_ when handling global variables in + `for` loops etc. When using Documenter with Julia 1.5 or above, Documenter uses the soft + scope in `@repl`-blocks and REPL-type doctests. + ## `@setup ` block These are similar to `@example` blocks, but both the input and output are hidden from the diff --git a/src/DocTests.jl b/src/DocTests.jl index 843ab1de04..e3b739f273 100644 --- a/src/DocTests.jl +++ b/src/DocTests.jl @@ -211,6 +211,11 @@ function eval_repl(block, sandbox, meta::Dict, doc::Documents.Document, page) for (ex, str) in Utilities.parseblock(input, doc, page; keywords = false, raise=false) # Input containing a semi-colon gets suppressed in the final output. result.hide = REPL.ends_with_semicolon(str) + if VERSION >= v"1.5.0-DEV.178" + # Use the REPL softscope for REPL jldoctests, + # see https://github.com/JuliaLang/julia/pull/33864 + ex = REPL.softscope!(ex) + end (value, success, backtrace, text) = Utilities.withoutput() do Core.eval(sandbox, ex) end diff --git a/src/Expanders.jl b/src/Expanders.jl index b6b106dac3..109ddf5558 100644 --- a/src/Expanders.jl +++ b/src/Expanders.jl @@ -608,6 +608,11 @@ function Selectors.runner(::Type{REPLBlocks}, x, page, doc) for (ex, str) in Utilities.parseblock(x.code, doc, page; keywords = false) buffer = IOBuffer() input = droplines(str) + if VERSION >= v"1.5.0-DEV.178" + # Use the REPL softscope for REPLBlocks, + # see https://github.com/JuliaLang/julia/pull/33864 + ex = REPL.softscope!(ex) + end (value, success, backtrace, text) = Utilities.withoutput() do cd(page.workdir) do Core.eval(mod, ex) diff --git a/test/doctests/doctests.jl b/test/doctests/doctests.jl index e85758eb08..b0eea4f0e6 100644 --- a/test/doctests/doctests.jl +++ b/test/doctests/doctests.jl @@ -73,7 +73,7 @@ function onormalize(s) s = replace(s, r"(@ Documenter.DocTests )(.*)$"m => s"\1{PATH}") # Remove stacktraces - s = replace(s, r"(│\s+Stacktrace:)(\n(│\s+)\[[0-9]+\].*)*" => s"\1\\n\3{STACKTRACE}") + s = replace(s, r"(│\s+Stacktrace:)(\n(│\s+)\[[0-9]+\].*)+" => s"\1\\n\3{STACKTRACE}") return s end @@ -211,6 +211,21 @@ rfile(filename) = joinpath(@__DIR__, "stdouts", filename) @test success @test is_same_as_file(output, rfile("32.stdout")) end + + if VERSION >= v"1.5.0-DEV.178" + # Julia 1.5 REPL softscope, + # see https://github.com/JuliaLang/julia/pull/33864 + run_makedocs(["softscope.md"]) do result, success, backtrace, output + @test success + @test is_same_as_file(output, rfile("41.stdout")) + end + else + # Old REPL scoping behaviour on older Julia version + run_makedocs(["hardscope.md"]) do result, success, backtrace, output + @test success + @test is_same_as_file(output, rfile("42.stdout")) + end + end end using Documenter.DocTests: remove_common_backtrace diff --git a/test/doctests/src/hardscope.md b/test/doctests/src/hardscope.md new file mode 100644 index 0000000000..73142f5f96 --- /dev/null +++ b/test/doctests/src/hardscope.md @@ -0,0 +1,51 @@ +REPL scoping behaviour when Julia < 1.5 + +```jldoctest; filter = r"Stacktrace:(\n \[[0-9]+\].*)+" +julia> s = 0 # global +0 + +julia> for i = 1:10 + t = s + i # new local `t` + s = t # assign global `s` + end +ERROR: UndefVarError: s not defined +Stacktrace: + [1] top-level scope at ./none:2 +[...] +``` + +```jldoctest; filter = r"Stacktrace:(\n \[[0-9]+\].*)+" +julia> code = """ + s = 0 # global + for i = 1:10 + t = s + i # new local `t` + s = t # new local `s` with warning + end + s, # global + @isdefined(t) # global + """; + +julia> include_string(Main, code) +ERROR: LoadError: UndefVarError: s not defined +Stacktrace: + [1] top-level scope at ./string:3 + [2] include_string(::Module, ::String, ::String) at ./loading.jl:1075 +[...] +``` + +```jldoctest; filter = r"Stacktrace:(\n \[[0-9]+\].*)+" +s = 0 # global +for i = 1:10 + t = s + i # new local `t` + s = t # new local `s` with warning +end +s, # global +@isdefined(t) # global + +# output + +ERROR: UndefVarError: s not defined +Stacktrace: + [1] top-level scope at ./none:2 +[...] +``` diff --git a/test/doctests/src/softscope.md b/test/doctests/src/softscope.md new file mode 100644 index 0000000000..4636c012c6 --- /dev/null +++ b/test/doctests/src/softscope.md @@ -0,0 +1,57 @@ +Julia 1.5's REPL softscope + +```jldoctest +julia> s = 0 # global +0 + +julia> for i = 1:10 + t = s + i # new local `t` + s = t # assign global `s` + end + +julia> s # global +55 + +julia> @isdefined(t) # global +false +``` + +```jldoctest; filter = r"Stacktrace:(\n \[[0-9]+\].*)*" +julia> code = """ + s = 0 # global + for i = 1:10 + t = s + i # new local `t` + s = t # new local `s` with warning + end + s, # global + @isdefined(t) # global + """; + +julia> include_string(Main, code) +┌ Warning: Assignment to `s` in soft scope is ambiguous because a global variable by the same name exists: `s` will be treated as a new local. Disambiguate by using `local s` to suppress this warning or `global s` to assign to the existing global variable. +└ @ string:4 +ERROR: LoadError: UndefVarError: s not defined +Stacktrace: + [1] top-level scope at ./string:3 + [2] include_string(::Module, ::String, ::String) at ./loading.jl:1080 +[...] +``` + +```jldoctest; filter = r"Stacktrace:(\n \[[0-9]+\].*)*" +s = 0 # global +for i = 1:10 + t = s + i # new local `t` + s = t # new local `s` with warning +end +s, # global +@isdefined(t) # global + +# output + +┌ Warning: Assignment to `s` in soft scope is ambiguous because a global variable by the same name exists: `s` will be treated as a new local. Disambiguate by using `local s` to suppress this warning or `global s` to assign to the existing global variable. +└ @ none:3 +ERROR: UndefVarError: s not defined +Stacktrace: + [1] top-level scope at ./none:2 +[...] +``` diff --git a/test/doctests/stdouts/41.stdout b/test/doctests/stdouts/41.stdout new file mode 100644 index 0000000000..bc921d27a2 --- /dev/null +++ b/test/doctests/stdouts/41.stdout @@ -0,0 +1,8 @@ +[ Info: SetupBuildDirectory: setting up build directory. +[ Info: Doctest: running doctests. +[ Info: ExpandTemplates: expanding markdown templates. +[ Info: CrossReferences: building cross-references. +[ Info: CheckDocument: running document checks. +[ Info: Populate: populating indices. +[ Info: RenderDocument: rendering document. +[ Info: HTMLWriter: rendering HTML pages. diff --git a/test/doctests/stdouts/42.stdout b/test/doctests/stdouts/42.stdout new file mode 100644 index 0000000000..bc921d27a2 --- /dev/null +++ b/test/doctests/stdouts/42.stdout @@ -0,0 +1,8 @@ +[ Info: SetupBuildDirectory: setting up build directory. +[ Info: Doctest: running doctests. +[ Info: ExpandTemplates: expanding markdown templates. +[ Info: CrossReferences: building cross-references. +[ Info: CheckDocument: running document checks. +[ Info: Populate: populating indices. +[ Info: RenderDocument: rendering document. +[ Info: HTMLWriter: rendering HTML pages.