Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

stdin/stdout/stderr and devnull #499

Merged
merged 5 commits into from
Feb 24, 2018
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,10 @@ 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]). To use `stdout`, `stdin`, or `stderr`, wrap your
code in the `@compat` macro.

## New macros

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

module Compat

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

@static if !isdefined(Base, :devnull) #25959
export devnull
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

5 space indent?

const devnull = DevNull
end

@static if !isdefined(Base, Symbol("@nospecialize"))
# 0.7
Expand Down Expand Up @@ -30,138 +35,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
136 changes: 136 additions & 0 deletions src/compatmacro.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# 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

@static if VERSION < v"0.7.0-DEV.4068" #25959
# stdin/stdout/stderr need to be rewritten with a macro — defining Compat.stdout=STDOUT
# won't work because the value of STDOUT can change at runtime via redirect_stdout etc.
_compat(s::Symbol) = s===:stdin ? :STDIN : s===:stdout ? :STDOUT : s===:stderr ? :STDERR : s
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")
@compat 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 @compat(all(x -> isa(x, IO), (devnull, stdin, stdout, stderr)))

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