It is a purely syntactical transformation.
func(x) do y
body
end
is equivalent to
func(y -> body, x)
or
tmp = y -> body
func(tmp, x)
if you will, i.e. the do block defines an anonymous function that is passed as the first argument. For this to work it is, of course, required that there exist a method of func that has a matching signature, for example func(f::Function, ...) = ... (or simplyfunc(f, ...) = ...).
You can inspect what is really happening by using Meta.@lower:
julia> Meta.@lower func(x) do y
print(y)
end
:($(Expr(:thunk, CodeInfo(
@ none within `top-level scope'
1 ─ $(Expr(:thunk, CodeInfo(
@ none within `top-level scope'
1 ─ global var"#1#2"
│ const var"#1#2"
│ %3 = Core._structtype(Main, Symbol("#1#2"), Core.svec(), Core.svec(), false, 0)
│ var"#1#2" = %3
│ Core._setsuper!(var"#1#2", Core.Function)
│ Core._typebody!(var"#1#2", Core.svec())
└── return nothing
)))
│ %2 = Core.svec(var"#1#2", Core.Any)
│ %3 = Core.svec()
│ %4 = Core.svec(%2, %3, $(QuoteNode(:(#= REPL[1]:2 =#))))
│ $(Expr(:method, false, :(%4), CodeInfo(
@ REPL[1]:2 within `none'
1 ─ %1 = print(y)
└── return %1
)))
│ #1 = %new(var"#1#2")
│ %7 = #1
│ %8 = func(%7, x)
└── return %8
))))
The output is, unfortunately, garbled with implementation details of anonymous functions: The code defines a new struct (%3), makes it callable (%4) using the body of the do block, creates an instance of it (%7), and in the end calls func(%7, x).