diff --git a/NEWS.md b/NEWS.md index dbe26e0f5fc44..74e95c9f32136 100644 --- a/NEWS.md +++ b/NEWS.md @@ -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 ---------------- diff --git a/doc/manual/types.rst b/doc/manual/types.rst index b5c44007df66e..d2e2f01bbb122 100644 --- a/doc/manual/types.rst +++ b/doc/manual/types.rst @@ -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: diff --git a/src/julia-parser.scm b/src/julia-parser.scm index 1244ee986c077..411c8ec98b6f3 100644 --- a/src/julia-parser.scm +++ b/src/julia-parser.scm @@ -946,11 +946,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) @@ -1183,7 +1188,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))) diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index aa5a6a5c1580e..feccff42c565e 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -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)) @@ -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) + `(ret-type ,R))))))))) ;; convert list of names (sl) and list of upper bounds to expressions that ;; construct TypeVars @@ -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 @@ -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 '() '()) @@ -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)) @@ -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)) @@ -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- @@ -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) @@ -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) @@ -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 @@ -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 "\""))) @@ -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 @@ -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) @@ -2953,6 +2971,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 @@ -2972,13 +2991,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)))) @@ -3203,6 +3225,11 @@ f(x) = yt(x) (emit-return temp) (emit temp)))) + ((ret-type) + (assert (not value)) + (assert (not rett)) + (set! rett (cadr e))) + ((&) (assert (and value (not tail))) `(& ,(compile (cadr e) break-labels value tail))) diff --git a/test/core.jl b/test/core.jl index 710afaa6722ca..364252651583e 100644 --- a/test/core.jl +++ b/test/core.jl @@ -4172,3 +4172,16 @@ function trigger14878() return w end @test_throws UndefVarError trigger14878() + +# 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)