Skip to content

Commit

Permalink
stdin/stdout/stderr and devnull (#499)
Browse files Browse the repository at this point in the history
* split at-compat macro into separate file

* stdin/stdout/stderr and devnull

* no more need for at-compat to use stdout etc

* Base._redirect_stdfoo returns nothing

* better yet, just copy the return value
  • Loading branch information
stevengj authored Feb 24, 2018
1 parent dcf9613 commit 7434380
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 130 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,9 @@ Currently, the `@compat` macro supports the following syntaxes:

* `object_id` is now `objectid` ([#25615]).

* `DevNull`, `STDIN`, `STDOUT` and `STDERR` are now `devnull`, `stdin`, `stdout` and
`stderr` respectively ([#25959]).

## New macros

* `@__DIR__` has been added ([#18380])
Expand Down
149 changes: 20 additions & 129 deletions src/Compat.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,26 @@ __precompile__()

module Compat

using Base.Meta
include("compatmacro.jl")

@static if !isdefined(Base, :devnull) #25959
export devnull, stdout, stdin, stderr
const devnull = DevNull
for f in (:stdout, :stdin, :stderr)
F = Symbol(uppercase(string(f)))
rf = Symbol(string("_redirect_", f))
@eval begin
$f = $F
# overload internal _redirect_std* functions
# so that they change Compat.std*
function Base.$rf(stream::IO)
ret = invoke(Base.$rf, Tuple{Any}, stream)
global $f = $F
return ret
end
end
end
end

@static if !isdefined(Base, Symbol("@nospecialize"))
# 0.7
Expand Down Expand Up @@ -30,138 +49,10 @@ using Base.Meta
export @nospecialize
end

"""Get just the function part of a function declaration."""
withincurly(ex) = isexpr(ex, :curly) ? ex.args[1] : ex

if VERSION < v"0.6.0-dev.2043"
Base.take!(t::Task) = consume(t)
end

is_index_style(ex::Expr) = ex == :(Compat.IndexStyle) || ex == :(Base.IndexStyle) ||
(ex.head == :(.) && (ex.args[1] == :Compat || ex.args[1] == :Base) &&
ex.args[2] == Expr(:quote, :IndexStyle))

is_index_style(arg) = false

istopsymbol(ex, mod, sym) = ex in (sym, Expr(:(.), mod, Expr(:quote, sym)))

if VERSION < v"0.6.0-dev.2782"
function new_style_typealias(ex::ANY)
isexpr(ex, :(=)) || return false
ex = ex::Expr
return length(ex.args) == 2 && isexpr(ex.args[1], :curly)
end
else
new_style_typealias(ex) = false
end

function _compat(ex::Expr)
if ex.head === :call
f = ex.args[1]
if VERSION < v"0.6.0-dev.826" && length(ex.args) == 3 && # julia#18510
istopsymbol(withincurly(ex.args[1]), :Base, :Nullable)
ex = Expr(:call, f, ex.args[2], Expr(:call, :(Compat._Nullable_field2), ex.args[3]))
end
elseif ex.head === :curly
f = ex.args[1]
if VERSION < v"0.6.0-dev.2575" #20414
ex = Expr(:curly, map(a -> isexpr(a, :call, 2) && a.args[1] == :(<:) ?
:($TypeVar($(QuoteNode(gensym(:T))), $(a.args[2]), false)) :
isexpr(a, :call, 2) && a.args[1] == :(>:) ?
:($TypeVar($(QuoteNode(gensym(:T))), $(a.args[2]), $Any, false)) : a,
ex.args)...)
end
elseif ex.head === :quote && isa(ex.args[1], Symbol)
# Passthrough
return ex
elseif new_style_typealias(ex)
ex.head = :typealias
elseif ex.head === :const && length(ex.args) == 1 && new_style_typealias(ex.args[1])
ex = ex.args[1]::Expr
ex.head = :typealias
end
if VERSION < v"0.6.0-dev.2840"
if ex.head == :(=) && isa(ex.args[1], Expr) && ex.args[1].head == :call
a = ex.args[1].args[1]
if is_index_style(a)
ex.args[1].args[1] = :(Base.linearindexing)
elseif isa(a, Expr) && a.head == :curly
if is_index_style(a.args[1])
ex.args[1].args[1].args[1] = :(Base.linearindexing)
end
end
end
end
if VERSION < v"0.7.0-DEV.880"
if ex.head == :curly && ex.args[1] == :CartesianRange && length(ex.args) >= 2
a = ex.args[2]
if a != :CartesianIndex && !(isa(a, Expr) && a.head == :curly && a.args[1] == :CartesianIndex)
return Expr(:curly, :CartesianRange, Expr(:curly, :CartesianIndex, ex.args[2]))
end
end
end
if VERSION < v"0.7.0-DEV.2562"
if ex.head == :call && ex.args[1] == :finalizer
ex.args[2], ex.args[3] = ex.args[3], ex.args[2]
end
end
return Expr(ex.head, map(_compat, ex.args)...)
end

_compat(ex) = ex

function _get_typebody(ex::Expr)
args = ex.args
if ex.head !== :type || length(args) != 3 || args[1] !== true
throw(ArgumentError("Invalid usage of @compat: $ex"))
end
name = args[2]
if !isexpr(args[3], :block)
throw(ArgumentError("Invalid type declaration: $ex"))
end
body = (args[3]::Expr).args
filter!(body) do e
if isa(e, LineNumberNode) || isexpr(e, :line)
return false
end
return true
end
return name, body
end

function _compat_primitive(typedecl)
name, body = _get_typebody(typedecl)
if length(body) != 1
throw(ArgumentError("Invalid primitive type declaration: $typedecl"))
end
return Expr(:bitstype, body[1], name)
end

function _compat_abstract(typedecl)
name, body = _get_typebody(typedecl)
if length(body) != 0
throw(ArgumentError("Invalid abstract type declaration: $typedecl"))
end
return Expr(:abstract, name)
end

macro compat(ex...)
if VERSION < v"0.6.0-dev.2746" && length(ex) == 2 && ex[1] === :primitive
return esc(_compat_primitive(ex[2]))
elseif length(ex) != 1
throw(ArgumentError("@compat called with wrong number of arguments: $ex"))
elseif (VERSION < v"0.6.0-dev.2746" && isexpr(ex[1], :abstract) &&
length(ex[1].args) == 1 && isexpr(ex[1].args[1], :type))
# This can in principle be handled in nested case but we do not
# do that to be consistent with primitive types.
return esc(_compat_abstract(ex[1].args[1]))
end
esc(_compat(ex[1]))
end


export @compat

# https://github.com/JuliaLang/julia/pull/22064
@static if !isdefined(Base, Symbol("@__MODULE__"))
# 0.7
Expand Down
130 changes: 130 additions & 0 deletions src/compatmacro.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# The @compat macro is used to implement compatibility rules that require
# syntax rewriting rather than simply new function/constant/module definitions.

using Base.Meta
export @compat

"""Get just the function part of a function declaration."""
withincurly(ex) = isexpr(ex, :curly) ? ex.args[1] : ex

is_index_style(ex::Expr) = ex == :(Compat.IndexStyle) || ex == :(Base.IndexStyle) ||
(ex.head == :(.) && (ex.args[1] == :Compat || ex.args[1] == :Base) &&
ex.args[2] == Expr(:quote, :IndexStyle))

is_index_style(arg) = false

istopsymbol(ex, mod, sym) = ex in (sym, Expr(:(.), mod, Expr(:quote, sym)))

if VERSION < v"0.6.0-dev.2782"
function new_style_typealias(ex::ANY)
isexpr(ex, :(=)) || return false
ex = ex::Expr
return length(ex.args) == 2 && isexpr(ex.args[1], :curly)
end
else
new_style_typealias(ex) = false
end

function _compat(ex::Expr)
if ex.head === :call
f = ex.args[1]
if VERSION < v"0.6.0-dev.826" && length(ex.args) == 3 && # julia#18510
istopsymbol(withincurly(ex.args[1]), :Base, :Nullable)
ex = Expr(:call, f, ex.args[2], Expr(:call, :(Compat._Nullable_field2), ex.args[3]))
end
elseif ex.head === :curly
f = ex.args[1]
if VERSION < v"0.6.0-dev.2575" #20414
ex = Expr(:curly, map(a -> isexpr(a, :call, 2) && a.args[1] == :(<:) ?
:($TypeVar($(QuoteNode(gensym(:T))), $(a.args[2]), false)) :
isexpr(a, :call, 2) && a.args[1] == :(>:) ?
:($TypeVar($(QuoteNode(gensym(:T))), $(a.args[2]), $Any, false)) : a,
ex.args)...)
end
elseif ex.head === :quote && isa(ex.args[1], Symbol)
# Passthrough
return ex
elseif new_style_typealias(ex)
ex.head = :typealias
elseif ex.head === :const && length(ex.args) == 1 && new_style_typealias(ex.args[1])
ex = ex.args[1]::Expr
ex.head = :typealias
end
if VERSION < v"0.6.0-dev.2840"
if ex.head == :(=) && isa(ex.args[1], Expr) && ex.args[1].head == :call
a = ex.args[1].args[1]
if is_index_style(a)
ex.args[1].args[1] = :(Base.linearindexing)
elseif isa(a, Expr) && a.head == :curly
if is_index_style(a.args[1])
ex.args[1].args[1].args[1] = :(Base.linearindexing)
end
end
end
end
if VERSION < v"0.7.0-DEV.880"
if ex.head == :curly && ex.args[1] == :CartesianRange && length(ex.args) >= 2
a = ex.args[2]
if a != :CartesianIndex && !(isa(a, Expr) && a.head == :curly && a.args[1] == :CartesianIndex)
return Expr(:curly, :CartesianRange, Expr(:curly, :CartesianIndex, ex.args[2]))
end
end
end
if VERSION < v"0.7.0-DEV.2562"
if ex.head == :call && ex.args[1] == :finalizer
ex.args[2], ex.args[3] = ex.args[3], ex.args[2]
end
end
return Expr(ex.head, map(_compat, ex.args)...)
end

_compat(ex) = ex

function _get_typebody(ex::Expr)
args = ex.args
if ex.head !== :type || length(args) != 3 || args[1] !== true
throw(ArgumentError("Invalid usage of @compat: $ex"))
end
name = args[2]
if !isexpr(args[3], :block)
throw(ArgumentError("Invalid type declaration: $ex"))
end
body = (args[3]::Expr).args
filter!(body) do e
if isa(e, LineNumberNode) || isexpr(e, :line)
return false
end
return true
end
return name, body
end

function _compat_primitive(typedecl)
name, body = _get_typebody(typedecl)
if length(body) != 1
throw(ArgumentError("Invalid primitive type declaration: $typedecl"))
end
return Expr(:bitstype, body[1], name)
end

function _compat_abstract(typedecl)
name, body = _get_typebody(typedecl)
if length(body) != 0
throw(ArgumentError("Invalid abstract type declaration: $typedecl"))
end
return Expr(:abstract, name)
end

macro compat(ex...)
if VERSION < v"0.6.0-dev.2746" && length(ex) == 2 && ex[1] === :primitive
return esc(_compat_primitive(ex[2]))
elseif length(ex) != 1
throw(ArgumentError("@compat called with wrong number of arguments: $ex"))
elseif (VERSION < v"0.6.0-dev.2746" && isexpr(ex[1], :abstract) &&
length(ex[1].args) == 1 && isexpr(ex[1].args[1], :type))
# This can in principle be handled in nested case but we do not
# do that to be consistent with primitive types.
return esc(_compat_abstract(ex[1].args[1]))
end
esc(_compat(ex[1]))
end
5 changes: 4 additions & 1 deletion test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ let filename = tempname()
@test chomp(read(filename, String)) == "hello"
ret = open(filename, "w") do f
redirect_stderr(f) do
println(STDERR, "WARNING: hello")
println(stderr, "WARNING: hello")
[2]
end
end
Expand Down Expand Up @@ -1416,6 +1416,9 @@ import Compat.Markdown
@test repr("text/plain", "string") == "\"string\"" #25990
@test showable("text/plain", 3.14159) #26089

# 25959
@test all(x -> isa(x, IO), (devnull, stdin, stdout, stderr))

# 0.7.0-DEV.3526
module TestNames
export foo
Expand Down

0 comments on commit 7434380

Please sign in to comment.