Skip to content

Commit

Permalink
Merge pull request #16432 from JuliaLang/jb/rettype
Browse files Browse the repository at this point in the history
add `function f()::T` return type declaration syntax
  • Loading branch information
JeffBezanson committed Jun 1, 2016
2 parents d07b849 + 1fc6bbc commit a4c419b
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 40 deletions.
3 changes: 3 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ New language features

* The syntax `x.:sym` (e.g. `Base.:+`) is now supported, and `x.(:sym)` is deprecated ([#15032]).

* Function return type syntax `function f()::T` has been added ([#1090]). Values returned
from a function with such a declaration will be converted to the specified type `T`.

Language changes
----------------

Expand Down
3 changes: 2 additions & 1 deletion base/docs/Docs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,7 @@ isquotedmacrocall(x) =
isexpr(x.args[1].value, :macrocall, 1)
# Simple expressions / atoms the may be documented.
isbasicdoc(x) = isexpr(x, :.) || isa(x, Union{QuoteNode, Symbol})
is_signature(x) = isexpr(x, :call) || (isexpr(x, :(::), 2) && isexpr(x.args[1], :call))

function docm(meta, ex, define = true)
# Some documented expressions may be decorated with macro calls which obscure the actual
Expand All @@ -621,7 +622,7 @@ function docm(meta, ex, define = true)
# function f end
# f(...)
#
isexpr(x, FUNC_HEADS) && isexpr(x.args[1], :call) ? objectdoc(meta, def, x, signature(x)) :
isexpr(x, FUNC_HEADS) && is_signature(x.args[1]) ? objectdoc(meta, def, x, signature(x)) :
isexpr(x, :function) && !isexpr(x.args[1], :call) ? objectdoc(meta, def, x) :
isexpr(x, :call) ? calldoc(meta, x) :

Expand Down
18 changes: 14 additions & 4 deletions doc/manual/types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,20 @@ The "declaration" behavior only occurs in specific contexts::

and applies to the whole current scope, even before the declaration.
Currently, type declarations cannot be used in global scope, e.g. in
the REPL, since Julia does not yet have constant-type globals. Note
that in a function return statement, the first two of the above
expressions compute a value and then ``::`` is a type assertion and
not a declaration.
the REPL, since Julia does not yet have constant-type globals.

Declarations can also be attached to function definitions::

function sinc(x)::Float64
if x == 0
return 1
end
return sin(pi*x)/(pi*x)
end

Returning from this function behaves just like an assignment to
a variable with a declared type: the value is always converted to
``Float64``.


.. _man-abstract-types:
Expand Down
18 changes: 13 additions & 5 deletions src/julia-parser.scm
Original file line number Diff line number Diff line change
Expand Up @@ -951,11 +951,16 @@
(parse-resword s ex)
(parse-call-chain s ex #f))))

(define (parse-def s strict)
(define (parse-def s is-func)
(let ((ex (parse-unary-prefix s)))
(if (or (and strict (reserved-word? ex)) (initial-reserved-word? ex))
(error (string "invalid name \"" ex "\""))
(parse-call-chain s ex #f))))
(let ((sig (if (or (and is-func (reserved-word? ex)) (initial-reserved-word? ex))
(error (string "invalid name \"" ex "\""))
(parse-call-chain s ex #f))))
(if (and is-func
(eq? (peek-token s) '|::|))
(begin (take-token s)
`(|::| ,sig ,(parse-call s)))
sig))))


(define (deprecated-dict-replacement ex)
Expand Down Expand Up @@ -1191,7 +1196,10 @@
(error (string "expected \"(\" in " word " definition")))
(if (not (and (pair? sig)
(or (eq? (car sig) 'call)
(eq? (car sig) 'tuple))))
(eq? (car sig) 'tuple)
(and (eq? (car sig) '|::|)
(pair? (cadr sig))
(eq? (car (cadr sig)) 'call)))))
(error (string "expected \"(\" in " word " definition"))
sig)))
(body (parse-block s)))
Expand Down
88 changes: 58 additions & 30 deletions src/julia-syntax.scm
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@

;; GF method does not need to keep decl expressions on lambda args
;; except for rest arg
(define (method-lambda-expr argl body)
(define (method-lambda-expr argl body rett)
(let ((argl (map (lambda (x)
(if (vararg? x)
(make-decl (arg-name x) (arg-type x))
Expand All @@ -164,9 +164,21 @@
x
`(|::| ,(arg-name x) (curly Vararg Any)))
(arg-name x))))
argl)))
argl))
(body (if (and (pair? body) (eq? (car body) 'block))
(if (null? (cdr body))
`(block (null))
body)
`(block ,body))))
`(lambda ,argl ()
(scope-block ,body))))
(scope-block
,(if (eq? rett 'Any)
body
(insert-after-meta
body
(let ((R (make-ssavalue)))
(list `(= ,R ,rett)
`(meta ret-type ,R)))))))))

;; convert list of names (sl) and list of upper bounds to expressions that
;; construct TypeVars
Expand Down Expand Up @@ -269,7 +281,7 @@

;; construct the (method ...) expression for one primitive method definition,
;; assuming optional and keyword args are already handled
(define (method-def-expr- name sparams argl body isstaged)
(define (method-def-expr- name sparams argl body isstaged (rett 'Any))
(if
(any kwarg? argl)
;; has optional positional args
Expand All @@ -289,7 +301,7 @@
(receive
(vararg req) (separate vararg? argl)
(optional-positional-defs name sparams req opt dfl body isstaged
(append req opt vararg))))))
(append req opt vararg) rett)))))
;; no optional positional args
(receive
(names bounds) (sparam-name-bounds sparams '() '())
Expand All @@ -306,7 +318,7 @@
(let* ((iscall (is-call-name? name))
(name (if iscall #f name))
(types (llist-types argl))
(body (method-lambda-expr argl body))
(body (method-lambda-expr argl body rett))
;; HACK: the typevars need to be bound to ssavalues, since this code
;; might be moved to a different scope by closure-convert.
(temps (map (lambda (x) (make-ssavalue)) names))
Expand Down Expand Up @@ -343,7 +355,7 @@
(or (number? x) (string? x) (char? x) (and (pair? x) (memq (car x) '(quote inert)))
(eq? x 'true) (eq? x 'false)))

(define (keywords-method-def-expr name sparams argl body isstaged)
(define (keywords-method-def-expr name sparams argl body isstaged rett)
(let* ((kargl (cdar argl)) ;; keyword expressions (= k v)
(pargl (cdr argl)) ;; positional args
(body (if (and (pair? body) (eq? (car body) 'block))
Expand Down Expand Up @@ -435,7 +447,7 @@
,@(cdr not-optional) ,@vararg)
`(block
,@lno
,@stmts) isstaged)
,@stmts) isstaged rett)

;; call with unsorted keyword args. this sorts and re-dispatches.
,(method-def-expr-
Expand Down Expand Up @@ -518,7 +530,7 @@
,(if (or (not (symbol? name)) (is-call-name? name))
'(null) name)))))

(define (optional-positional-defs name sparams req opt dfl body isstaged overall-argl)
(define (optional-positional-defs name sparams req opt dfl body isstaged overall-argl rett)
;; prologue includes line number node and eventual meta nodes
(let ((prologue (if (pair? body)
(take-while (lambda (e)
Expand Down Expand Up @@ -558,7 +570,7 @@
(call ,(arg-name (car req)) ,@(map arg-name (cdr passed)) ,@vals)))))
(method-def-expr- name sp passed body #f)))
(iota (length opt)))
,(method-def-expr- name sparams overall-argl body isstaged))))
,(method-def-expr- name sparams overall-argl body isstaged rett))))

;; strip empty (parameters ...), normalizing `f(x;)` to `f(x)`.
(define (remove-empty-parameters argl)
Expand Down Expand Up @@ -586,14 +598,14 @@
;; definitions without keyword arguments are passed to method-def-expr-,
;; which handles optional positional arguments by adding the needed small
;; boilerplate definitions.
(define (method-def-expr name sparams argl body isstaged)
(define (method-def-expr name sparams argl body isstaged rett)
(let ((argl (remove-empty-parameters argl)))
(if (has-parameters? argl)
;; has keywords
(begin (check-kw-args (cdar argl))
(keywords-method-def-expr name sparams argl body isstaged))
(keywords-method-def-expr name sparams argl body isstaged rett))
;; no keywords
(method-def-expr- name sparams argl body isstaged))))
(method-def-expr- name sparams argl body isstaged rett))))

(define (struct-def-expr name params super fields mut)
(receive
Expand Down Expand Up @@ -873,7 +885,10 @@
(list* g (if isamp `(& ,ca) ca) C))))))))

(define (expand-function-def e) ;; handle function or stagedfunction
(let ((name (cadr e)))
(let* ((name (cadr e))
(dcl (and (pair? name) (eq? (car name) '|::|)))
(rett (if dcl (caddr name) 'Any))
(name (if dcl (cadr name) name)))
(cond ((and (length= e 2) (symbol? name))
(if (or (eq? name 'true) (eq? name 'false))
(error (string "invalid function name \"" name "\"")))
Expand Down Expand Up @@ -904,7 +919,7 @@
(eq? (caar argl) 'parameters))))))
(name (if (decl? name) #f name)))
(expand-forms
(method-def-expr name sparams argl (caddr e) isstaged))))
(method-def-expr name sparams argl (caddr e) isstaged rett))))
(else e))))

;; handle ( )->( ) function expressions. blocks `(a;b=1)` on the left need to be
Expand Down Expand Up @@ -1563,7 +1578,10 @@
(define lhs (cadr e))
(cond
((and (pair? lhs)
(eq? (car lhs) 'call))
(or (eq? (car lhs) 'call)
(and (eq? (car lhs) '|::|)
(pair? (cadr lhs))
(eq? (car (cadr lhs)) 'call))))
(expand-forms (cons 'function (cdr e))))
((and (pair? lhs)
(eq? (car lhs) 'comparison)
Expand Down Expand Up @@ -2954,6 +2972,7 @@ f(x) = yt(x)
(let ((code '())
(filename #f)
(first-line #t)
(rett #f)
(label-counter 0) ;; counter for generating label addresses
(label-map (table)) ;; maps label names to generated addresses
(label-level (table)) ;; exception handler level of each label
Expand All @@ -2973,13 +2992,16 @@ f(x) = yt(x)
(mark-label l)
l)))
(define (emit-return x)
(if (> handler-level 0)
(let ((tmp (if (or (simple-atom? x) (ssavalue? x) (equal? x '(null)))
#f (make-ssavalue))))
(if tmp (emit `(= ,tmp ,x)))
(emit `(leave ,handler-level))
(emit `(return ,(or tmp x))))
(emit `(return ,x))))
(let ((rv (if (> handler-level 0)
(let ((tmp (if (or (simple-atom? x) (ssavalue? x) (equal? x '(null)))
#f (make-ssavalue))))
(if tmp (emit `(= ,tmp ,x)))
(emit `(leave ,handler-level))
(or tmp x))
x)))
(if rett
(emit `(return ,(convert-for-type-decl rv rett)))
(emit `(return ,rv)))))
(define (new-mutable-var)
(let ((g (gensy)))
(set-car! (lam:vinfo lam) (append (car (lam:vinfo lam)) `((,g Any 2))))
Expand Down Expand Up @@ -3233,16 +3255,22 @@ f(x) = yt(x)
;; top level expressions returning values
((abstract_type bits_type composite_type thunk toplevel module)
(if tail (emit-return e) (emit e)))

;; other top level expressions and metadata
((import importall using export line meta inbounds boundscheck simdloop)
(let ((have-ret? (and (pair? code) (pair? (car code)) (eq? (caar code) 'return))))
(if (eq? (car e) 'line)
(if first-line
(begin (set! first-line #f)
(emit e))
;; strip filenames out of non-initial line nodes
(emit `(line ,(cadr e))))
(emit e))
(cond ((eq? (car e) 'line)
(if first-line
(begin (set! first-line #f)
(emit e))
;; strip filenames out of non-initial line nodes
(emit `(line ,(cadr e)))))
((and (eq? (car e) 'meta) (length> e 2) (eq? (cadr e) 'ret-type))
(assert (not value))
(assert (not rett))
(set! rett (caddr e)))
(else
(emit e)))
(if (and tail (not have-ret?))
(emit-return '(null)))
'(null)))
Expand Down
13 changes: 13 additions & 0 deletions test/core.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4190,3 +4190,16 @@ let nometh = expand(:(A15838.@f(1, 2)))
@test e.f === getfield(A15838, Symbol("@f"))
@test e.args === (1,2)
end

# issue #1090
function f1090(x)::Int
if x == 1
return 1
end
2.0
end
@test f1090(1) === 1
@test f1090(2) === 2
g1090{T}(x::T)::T = x+1.0
@test g1090(1) === 2
@test g1090(Float32(3)) === Float32(4)
13 changes: 13 additions & 0 deletions test/docs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,14 @@ const val = Foo(1.0)
function multidoc end,
function multidoc! end

"returntype-1"
returntype(x::Float64)::Float64 = x

"returntype-2"
function returntype(x::Int)::Int
x
end

end

let md = meta(DocsTest)[@var(DocsTest)]
Expand Down Expand Up @@ -223,6 +231,11 @@ let IT = @var(DocsTest.IT)
@test d.data[:fields][:y] == "IT.y"
end

let rt = @var(DocsTest.returntype)
md = meta(DocsTest)[rt]
@test md.order == [Tuple{Float64}, Tuple{Int}]
end

@test docstrings_equal(@doc(DocsTest.TA), doc"TA")

@test docstrings_equal(@doc(DocsTest.@mac), doc"@mac()")
Expand Down

0 comments on commit a4c419b

Please sign in to comment.