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

Support @opaque Tuple{T,U...}->RT (...)->... syntax for explicit arg/return types #54947

Merged
merged 1 commit into from
Jul 4, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
16 changes: 14 additions & 2 deletions base/opaque_closure.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,23 @@ the argument type may be fixed length even if the function is variadic.
This interface is experimental and subject to change or removal without notice.
"""
macro opaque(ex)
esc(Expr(:opaque_closure, ex))
esc(Expr(:opaque_closure, nothing, nothing, nothing, ex))
end

macro opaque(ty, ex)
esc(Expr(:opaque_closure, ty, ex))
if Base.isexpr(ty, :->)
(AT, body) = ty.args
filter!((n)->!isa(n, Core.LineNumberNode), body.args)
if !Base.isexpr(body, :block) || length(body.args) != 1
error("Opaque closure type must be specified in the form Tuple{T,U...}->RT")
end
RT = only(body.args)
else
error("Opaque closure type must be specified in the form Tuple{T,U...}->RT")
end
AT = (AT !== :_) ? AT : nothing
RT = (RT !== :_) ? RT : nothing
return esc(Expr(:opaque_closure, AT, RT, RT, ex))
Copy link
Sponsor Member

Choose a reason for hiding this comment

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

Shouldn't rt_lb be Union{} instead of RT?

Copy link
Member Author

Choose a reason for hiding this comment

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

That would allow the optimizer to change the return type, but the purpose of this syntax is to guarantee that the optimizer won't change the type on you.

e.g. you need to generate an OpaqueClosure{Tuple{Int,Int},Int} because it will be put into a container - an OpaqueClosure{Tuple{Int,Int},Union{}} would be the wrong thing to generate

end

# OpaqueClosure construction from pre-inferred CodeInfo/IRCode
Expand Down
2 changes: 1 addition & 1 deletion base/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3335,8 +3335,8 @@ bitstring(B::BitArray) = sprint(bitshow, B)
function show(io::IO, oc::Core.OpaqueClosure)
A, R = typeof(oc).parameters
show_tuple_as_call(io, Symbol(""), A; hasfirst=false)
print(io, "::", R)
print(io, "->◌")
print(io, "::", R)
end

function show(io::IO, ::MIME"text/plain", oc::Core.OpaqueClosure{A, R}) where {A, R}
Expand Down
32 changes: 23 additions & 9 deletions src/julia-syntax.scm
Original file line number Diff line number Diff line change
Expand Up @@ -2403,6 +2403,18 @@
(cons (car e)
(map expand-forms (cdr e)))))))

(define (find pred e)
(let loop ((xs e))
(if (null? xs)
#f
(let ((elt (car xs)))
(if (pred elt)
elt
(loop (cdr xs)))))))

(define (something e)
(find (lambda (x) (not (equal? x '(null)))) e))

;; table mapping expression head to a function expanding that form
(define expand-table
(table
Expand All @@ -2422,13 +2434,15 @@

'opaque_closure
(lambda (e)
(let* ((ty (and (length> e 2) (expand-forms (cadr e))))
(F (if (length> e 2) (caddr e) (cadr e)))
(let* ((argt (something (list (expand-forms (cadr e)) #f)))
(rt_lb (something (list (expand-forms (caddr e)) #f)))
(rt_ub (something (list (expand-forms (cadddr e)) #f)))
(F (caddddr e))
(isva (let* ((arglist (function-arglist F))
(lastarg (and (pair? arglist) (last arglist))))
(if (and ty (any (lambda (arg)
(if (and argt (any (lambda (arg)
(let ((arg (if (vararg? arg) (cadr arg) arg)))
(not (equal? (arg-type arg) '(core Any)))))
(not (symbol? arg))))
arglist))
(error "Opaque closure argument type may not be specified both in the method signature and separately"))
(if (or (varargexpr? lastarg) (vararg? lastarg))
Expand All @@ -2448,7 +2462,7 @@
(let* ((argtype (foldl (lambda (var ex) `(call (core UnionAll) ,var ,ex))
(expand-forms `(curly (core Tuple) ,@argtypes))
(reverse tvars))))
`(_opaque_closure ,(or ty argtype) ,isva ,(length argtypes) ,functionloc ,lam))))
`(_opaque_closure ,(or argt argtype) ,rt_lb ,rt_ub ,isva ,(length argtypes) ,functionloc ,lam))))

'block
(lambda (e)
Expand Down Expand Up @@ -4014,9 +4028,9 @@ f(x) = yt(x)
e))
(else e))))
((_opaque_closure)
(let* ((isva (caddr e))
(nargs (cadddr e))
(functionloc (caddddr e))
(let* ((isva (car (cddddr e)))
(nargs (cadr (cddddr e)))
(functionloc (caddr (cddddr e)))
(lam2 (last e))
(vis (lam:vinfo lam2))
(cvs (map car (cadr vis))))
Expand All @@ -4028,7 +4042,7 @@ f(x) = yt(x)
v)))
cvs)))
`(new_opaque_closure
,(cadr e) (call (core apply_type) (core Union)) (core Any) (true)
,(cadr e) ,(or (caddr e) '(call (core apply_type) (core Union))) ,(or (cadddr e) '(core Any)) (true)
(opaque_closure_method (null) ,nargs ,isva ,functionloc ,(convert-lambda lam2 (car (lam:args lam2)) #f '() (symbol-to-idx-map cvs)))
,@var-exprs))))
((method)
Expand Down
2 changes: 1 addition & 1 deletion stdlib/InteractiveUtils/test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ tag = "ANY"
@test !warntype_hastag(ImportIntrinsics15819.sqrt15819, Tuple{Float32}, tag)

@testset "code_warntype OpaqueClosure" begin
g = Base.Experimental.@opaque Tuple{Float64} x -> 0.0
g = Base.Experimental.@opaque Tuple{Float64}->_ x -> 0.0
@test warntype_hastag(g, Tuple{Float64}, "::Float64")
end

Expand Down
31 changes: 19 additions & 12 deletions test/opaque_closure.jl
Original file line number Diff line number Diff line change
Expand Up @@ -151,34 +151,41 @@ end # module test_world_age

function maybe_vararg(isva::Bool)
T = isva ? Vararg{Int} : Int
@opaque Tuple{T} (x...)->x
@opaque Tuple{T}->_ (x...)->x
end
@test maybe_vararg(false)(1) == (1,)
@test_throws MethodError maybe_vararg(false)(1,2,3)
@test maybe_vararg(true)(1) == (1,)
@test maybe_vararg(true)(1,2,3) == (1,2,3)
@test (@opaque Tuple{Int, Int} (a, b, x...)->x)(1,2) === ()
@test (@opaque Tuple{Int, Int} (a, x...)->x)(1,2) === (2,)
@test (@opaque Tuple{Int, Vararg{Int}} (a, x...)->x)(1,2,3,4) === (2,3,4)
@test (@opaque Tuple{Int, Int}->_ (a, b, x...)->x)(1,2) === ()
@test (@opaque Tuple{Int, Int}->Tuple{} (a, b, x...)->x)(1,2) === ()
@test (@opaque _->Tuple{Vararg{Int}} (a, b, x...)->x)(1,2) === ()
@test (@opaque Tuple{Int, Int}->_ (a, x...)->x)(1,2) === (2,)
@test (@opaque Tuple{Int, Int}->Tuple{Int} (a, x...)->x)(1,2) === (2,)
@test (@opaque _->Tuple{Vararg{Int}} (a, x...)->x)(1,2) === (2,)
@test (@opaque Tuple{Int, Vararg{Int}}->_ (a, x...)->x)(1,2,3,4) === (2,3,4)
@test (@opaque Tuple{Int, Vararg{Int}}->Tuple{Vararg{Int}} (a, x...)->x)(1,2,3,4) === (2,3,4)
@test (@opaque (a::Int, x::Int...)->x)(1,2,3) === (2,3)
@test (@opaque _->Tuple{Vararg{Int}} (a::Int, x::Int...)->x)(1,2,3) === (2,3)
@test (@opaque _->_ (a::Int, x::Int...)->x)(1,2,3) === (2,3)

@test_throws ErrorException (@opaque Tuple{Vararg{Int}} x->x)
@test_throws ErrorException (@opaque Tuple{Int, Vararg{Int}} x->x)
@test_throws ErrorException (@opaque Tuple{Int, Int} x->x)
@test_throws ErrorException (@opaque Tuple{Any} (x,y)->x)
@test_throws ErrorException (@opaque Tuple{Vararg{Int}} (x,y...)->x)
@test_throws ErrorException (@opaque Tuple{Int} (x,y,z...)->x)
@test_throws ErrorException (@opaque Tuple{Vararg{Int}}->_ x->x)
@test_throws ErrorException (@opaque Tuple{Int, Vararg{Int}}->_ x->x)
@test_throws ErrorException (@opaque Tuple{Int, Int}->_ x->x)
@test_throws ErrorException (@opaque Tuple{Any}->_ (x,y)->x)
@test_throws ErrorException (@opaque Tuple{Vararg{Int}}->_ (x,y...)->x)
@test_throws ErrorException (@opaque Tuple{Int}->_ (x,y,z...)->x)

# cannot specify types both on arguments and separately
@test_throws ErrorException @eval @opaque Tuple{Any} (x::Int)->x
@test_throws ErrorException @eval @opaque Tuple{Any}->_ (x::Int)->x

# Vargarg in complied mode
mk_va_opaque() = @opaque (x...)->x
@test mk_va_opaque()(1) == (1,)
@test mk_va_opaque()(1,2) == (1,2)

# OpaqueClosure show method
@test repr(@opaque x->Base.inferencebarrier(1)) == "(::Any)::Any->◌"
@test repr(@opaque x->Base.inferencebarrier(1)) == "(::Any)->◌::Any"

# Opaque closure in CodeInfo returned from generated functions
let ci = @code_lowered const_int()
Expand Down