diff --git a/base/docs/basedocs.jl b/base/docs/basedocs.jl index d3ab36cf898da..98a6a2ced9fdf 100644 --- a/base/docs/basedocs.jl +++ b/base/docs/basedocs.jl @@ -2542,18 +2542,6 @@ Retrieve the declared type of the binding `name` from the module `module`. """ Core.get_binding_type -""" - Core.set_binding_type!(module::Module, name::Symbol, [type::Type]) - -Set the declared type of the binding `name` in the module `module` to `type`. Error if the -binding already has a type that is not equivalent to `type`. If the `type` argument is -absent, set the binding type to `Any` if unset, but do not error. - -!!! compat "Julia 1.9" - This function requires Julia 1.9 or later. -""" -Core.set_binding_type! - """ swapglobal!(module::Module, name::Symbol, x, [order::Symbol=:monotonic]) diff --git a/doc/src/devdocs/builtins.md b/doc/src/devdocs/builtins.md index a572bfa39c7d2..e53321f3e70a0 100644 --- a/doc/src/devdocs/builtins.md +++ b/doc/src/devdocs/builtins.md @@ -22,7 +22,6 @@ Core.Intrinsics.atomic_pointerswap Core.Intrinsics.atomic_pointermodify Core.Intrinsics.atomic_pointerreplace Core.get_binding_type -Core.set_binding_type! Core.IntrinsicFunction Core.Intrinsics Core.IR diff --git a/src/ast.c b/src/ast.c index c2090c3a465d2..7c775bf25d486 100644 --- a/src/ast.c +++ b/src/ast.c @@ -60,6 +60,7 @@ JL_DLLEXPORT jl_sym_t *jl_thunk_sym; JL_DLLEXPORT jl_sym_t *jl_foreigncall_sym; JL_DLLEXPORT jl_sym_t *jl_as_sym; JL_DLLEXPORT jl_sym_t *jl_global_sym; +JL_DLLEXPORT jl_sym_t *jl_globaldecl_sym; JL_DLLEXPORT jl_sym_t *jl_local_sym; JL_DLLEXPORT jl_sym_t *jl_list_sym; JL_DLLEXPORT jl_sym_t *jl_dot_sym; @@ -355,6 +356,7 @@ void jl_init_common_symbols(void) jl_opaque_closure_method_sym = jl_symbol("opaque_closure_method"); jl_const_sym = jl_symbol("const"); jl_global_sym = jl_symbol("global"); + jl_globaldecl_sym = jl_symbol("globaldecl"); jl_local_sym = jl_symbol("local"); jl_thunk_sym = jl_symbol("thunk"); jl_toplevel_sym = jl_symbol("toplevel"); diff --git a/src/builtin_proto.h b/src/builtin_proto.h index b0536bef24e27..8b97c46df72da 100644 --- a/src/builtin_proto.h +++ b/src/builtin_proto.h @@ -79,7 +79,6 @@ JL_CALLABLE(jl_f__primitivetype); JL_CALLABLE(jl_f__setsuper); JL_CALLABLE(jl_f__equiv_typedef); JL_CALLABLE(jl_f_get_binding_type); -JL_CALLABLE(jl_f_set_binding_type); JL_CALLABLE(jl_f__compute_sparams); JL_CALLABLE(jl_f__svec_ref); #ifdef __cplusplus diff --git a/src/builtins.c b/src/builtins.c index 1ac51da1ce2df..7f75b28a8a50a 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -1384,27 +1384,6 @@ JL_CALLABLE(jl_f_get_binding_type) return ty; } -JL_CALLABLE(jl_f_set_binding_type) -{ - JL_NARGS(set_binding_type!, 2, 3); - jl_module_t *m = (jl_module_t*)args[0]; - jl_sym_t *s = (jl_sym_t*)args[1]; - JL_TYPECHK(set_binding_type!, module, (jl_value_t*)m); - JL_TYPECHK(set_binding_type!, symbol, (jl_value_t*)s); - jl_value_t *ty = nargs == 2 ? (jl_value_t*)jl_any_type : args[2]; - JL_TYPECHK(set_binding_type!, type, ty); - jl_binding_t *b = jl_get_binding_wr(m, s, 0); - jl_value_t *old_ty = NULL; - if (jl_atomic_cmpswap_relaxed(&b->ty, &old_ty, ty)) { - jl_gc_wb(b, ty); - } - else if (nargs != 2 && !jl_types_equal(ty, old_ty)) { - jl_errorf("cannot set type for global %s.%s. It already has a value or is already set to a different type.", - jl_symbol_name(m->name), jl_symbol_name(s)); - } - return jl_nothing; -} - JL_CALLABLE(jl_f_swapglobal) { enum jl_memory_order order = jl_memory_order_release; @@ -2416,7 +2395,6 @@ void jl_init_primitives(void) JL_GC_DISABLED jl_builtin_getglobal = add_builtin_func("getglobal", jl_f_getglobal); jl_builtin_setglobal = add_builtin_func("setglobal!", jl_f_setglobal); add_builtin_func("get_binding_type", jl_f_get_binding_type); - add_builtin_func("set_binding_type!", jl_f_set_binding_type); jl_builtin_swapglobal = add_builtin_func("swapglobal!", jl_f_swapglobal); jl_builtin_replaceglobal = add_builtin_func("replaceglobal!", jl_f_replaceglobal); jl_builtin_modifyglobal = add_builtin_func("modifyglobal!", jl_f_modifyglobal); diff --git a/src/codegen.cpp b/src/codegen.cpp index 65ea216e19dd1..e7fdf76d946c8 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -932,6 +932,24 @@ static const auto jldeclareconst_func = new JuliaFunction<>{ {T_pjlvalue, T_pjlvalue, T_pjlvalue}, false); }, nullptr, }; +static const auto jldeclareconstval_func = new JuliaFunction<>{ + XSTR(jl_declare_constant_val), + [](LLVMContext &C) { + auto T_pjlvalue = JuliaType::get_pjlvalue_ty(C); + auto T_prjlvalue = JuliaType::get_prjlvalue_ty(C); + return FunctionType::get(getVoidTy(C), + {T_pjlvalue, T_pjlvalue, T_pjlvalue, T_prjlvalue}, false); }, + nullptr, +}; +static const auto jldeclareglobal_func = new JuliaFunction<>{ + XSTR(jl_declare_global), + [](LLVMContext &C) { + auto T_pjlvalue = JuliaType::get_pjlvalue_ty(C); + auto T_prjlvalue = JuliaType::get_prjlvalue_ty(C); + return FunctionType::get(getVoidTy(C), + {T_pjlvalue, T_pjlvalue, T_prjlvalue}, false); }, + nullptr, +}; static const auto jlgetbindingorerror_func = new JuliaFunction<>{ XSTR(jl_get_binding_or_error), [](LLVMContext &C) { @@ -6461,7 +6479,7 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaidx_ return meth; } else if (head == jl_const_sym) { - assert(nargs == 1); + assert(nargs <= 2); jl_sym_t *sym = (jl_sym_t*)args[0]; jl_module_t *mod = ctx.module; if (jl_is_globalref(sym)) { @@ -6471,10 +6489,29 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaidx_ if (jl_is_symbol(sym)) { jl_binding_t *bnd = NULL; Value *bp = global_binding_pointer(ctx, mod, sym, &bnd, true, true); - if (bp) - ctx.builder.CreateCall(prepare_call(jldeclareconst_func), - { bp, literal_pointer_val(ctx, (jl_value_t*)mod), literal_pointer_val(ctx, (jl_value_t*)sym) }); + if (bp) { + if (nargs == 2) { + jl_cgval_t rhs = emit_expr(ctx, args[1]); + ctx.builder.CreateCall(prepare_call(jldeclareconstval_func), + { bp, literal_pointer_val(ctx, (jl_value_t*)mod), literal_pointer_val(ctx, (jl_value_t*)sym), boxed(ctx, rhs) }); + } else { + ctx.builder.CreateCall(prepare_call(jldeclareconst_func), + { bp, literal_pointer_val(ctx, (jl_value_t*)mod), literal_pointer_val(ctx, (jl_value_t*)sym) }); + } + } + } + } + else if (head == jl_globaldecl_sym) { + assert(nargs == 2); + jl_sym_t *sym = (jl_sym_t*)args[0]; + jl_module_t *mod = ctx.module; + if (jl_is_globalref(sym)) { + mod = jl_globalref_mod(sym); + sym = jl_globalref_name(sym); } + jl_cgval_t typ = emit_expr(ctx, args[1]); + ctx.builder.CreateCall(prepare_call(jldeclareglobal_func), + { literal_pointer_val(ctx, (jl_value_t*)mod), literal_pointer_val(ctx, (jl_value_t*)sym), boxed(ctx, typ) }); } else if (head == jl_new_sym) { bool is_promotable = false; diff --git a/src/interpreter.c b/src/interpreter.c index 76bf585b72c39..472c6312691ba 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -628,6 +628,18 @@ static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s, size_t ip, jl_value_t *res = jl_toplevel_eval(s->module, stmt); s->locals[jl_source_nslots(s->src) + s->ip] = res; } + else if (head == jl_globaldecl_sym) { + jl_value_t *val = eval_value(jl_exprarg(stmt, 1), s); + s->locals[jl_source_nslots(s->src) + s->ip] = val; // temporarily root + jl_declare_global(s->module, jl_exprarg(stmt, 0), val); + s->locals[jl_source_nslots(s->src) + s->ip] = jl_nothing; + } + else if (head == jl_const_sym) { + jl_value_t *val = jl_expr_nargs(stmt) == 1 ? NULL : eval_value(jl_exprarg(stmt, 1), s); + s->locals[jl_source_nslots(s->src) + s->ip] = val; // temporarily root + jl_eval_const_decl(s->module, jl_exprarg(stmt, 0), val); + s->locals[jl_source_nslots(s->src) + s->ip] = jl_nothing; + } else if (jl_is_toplevel_only_expr(stmt)) { jl_toplevel_eval(s->module, stmt); } diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 5a50bb0cba41a..623474353f580 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -985,7 +985,9 @@ (error (string "field name \"" (deparse v) "\" is not a symbol")))) field-names) `(block - (global ,name) (const ,name) + ;; This is to prevent the :isdefined below from resolving the binding to an import. + ;; This will be reworked to a different check with world-age partitioned bindings. + (global ,name) (scope-block (block (hardscope) @@ -1013,8 +1015,8 @@ (quote parameters)))) '())) ;; otherwise do an assignment to trigger an error - (= (globalref (thismodule) ,name) ,name))) - (= (globalref (thismodule) ,name) ,name)) + (const (globalref (thismodule) ,name) ,name))) + (const (globalref (thismodule) ,name) ,name)) (call (core _typebody!) ,name (call (core svec) ,@field-types)) (null))) ;; "inner" constructors @@ -1051,7 +1053,7 @@ (receive (params bounds) (sparam-name-bounds params) `(block - (global ,name) (const ,name) + (global ,name) (scope-block (block (local-def ,name) @@ -1064,14 +1066,14 @@ (if (&& (isdefined (globalref (thismodule) ,name)) (call (core _equiv_typedef) (globalref (thismodule) ,name) ,name)) (null) - (= (globalref (thismodule) ,name) ,name)) + (const (globalref (thismodule) ,name) ,name)) (null)))))) (define (primitive-type-def-expr n name params super) (receive (params bounds) (sparam-name-bounds params) `(block - (global ,name) (const ,name) + (global ,name) (scope-block (block (local-def ,name) @@ -1084,7 +1086,7 @@ (if (&& (isdefined (globalref (thismodule) ,name)) (call (core _equiv_typedef) (globalref (thismodule) ,name) ,name)) (null) - (= (globalref (thismodule) ,name) ,name)) + (const (globalref (thismodule) ,name) ,name)) (null)))))) ;; take apart a type signature, e.g. T{X} <: S{Y} @@ -1441,34 +1443,57 @@ (else (error "invalid \"try\" form"))))) -(define (expand-unionall-def name type-ex) +(define (expand-unionall-def name type-ex (allow-local #t)) (if (and (pair? name) (eq? (car name) 'curly)) (let ((name (cadr name)) - (params (cddr name))) + (params (cddr name)) + (rr (make-ssavalue))) (if (null? params) (error (string "empty type parameter list in \"" (deparse `(= (curly ,name) ,type-ex)) "\""))) - `(block - (const-if-global ,name) - ,(expand-forms - `(= ,name (where ,type-ex ,@params))))) + (expand-forms + `(block + (= ,rr (where ,type-ex ,@params)) + (,(if allow-local 'assign-const-if-global 'const) ,name ,rr) + ,rr))) (expand-forms `(const (= ,name ,type-ex))))) -;; take apart e.g. `const a::Int = 0` into `const a; a::Int = 0` -(define (expand-const-decl e) - (let ((arg (cadr e))) - (if (atom? arg) - e - (case (car arg) - ((global local local-def) - (for-each (lambda (b) (if (not (assignment? b)) - (error "expected assignment after \"const\""))) - (cdr arg)) - (expand-forms (expand-decls (car arg) (cdr arg) #t))) - ((= |::|) - (expand-forms (expand-decls 'const (cdr e) #f))) - (else (error "expected assignment after \"const\"")))))) +(define (filter-not-underscore syms) + (filter (lambda (x) (not (underscore-symbol? x))) syms)) + +;; Expand `[global] const a::T = val` +(define (expand-const-decl e (mustassgn #f)) + (if (length= e 3) e + (let ((arg (cadr e))) + (if (atom? arg) + (if mustassgn + (error "expected assignment after \"const\"") + e) + (case (car arg) + ((global) + (expand-const-decl `(const ,(cadr arg)) #t)) + ((=) + (cond + ;; `const f() = ...` - The `const` here is inoperative, but the syntax happened to work in earlier versions, so simply strip `const`. + ;; TODO: Consider whether to keep this in 2.0. + ((eventually-call? (cadr arg)) + (expand-forms arg)) + ((and (pair? (cadr arg)) (eq? (caadr arg) 'curly)) + (expand-unionall-def (cadr arg) (caddr arg))) + ((and (pair? (cadr arg)) (eq? (caadr arg) 'tuple) (not (has-parameters? (cdr (cadr arg))))) + ;; We need this case because `(f(), g()) = (1, 2)` goes through here, which cannot go via the `local` lowering below, + ;; because the symbols come out wrong. Sigh... So much effort for such a syntax corner case. + (expand-tuple-destruct (cdr (cadr arg)) (caddr arg) (lambda (assgn) `(,(car e) ,assgn)))) + (else + (let ((rr (make-ssavalue))) + (expand-forms `(block + (= ,rr ,(caddr arg)) + (scope-block (block (hardscope) + (local (= ,(cadr arg) ,rr)) + ,.(map (lambda (v) `(,(car e) (globalref (thismodule) ,v) ,v)) (filter-not-underscore (lhs-vars (cadr arg)))) + ,rr)))))))) + (else (error "expected assignment after \"const\""))))))) (define (expand-atomic-decl e) (error "unimplemented or unsupported atomic declaration")) @@ -1476,7 +1501,7 @@ (define (expand-local-or-global-decl e) (if (and (symbol? (cadr e)) (length= e 2)) e - (expand-forms (expand-decls (car e) (cdr e) #f)))) + (expand-forms (expand-decls (car e) (cdr e))))) ;; given a complex assignment LHS, return the symbol that will ultimately be assigned to (define (assigned-name e) @@ -1486,37 +1511,36 @@ (assigned-name (cadr e))) (else e))) -;; local x, y=2, z => local x;local y;local z;y = 2 -(define (expand-decls what binds const?) +;; local x, (y=2), z => local x;local y;local z;y = 2 +(define (expand-decls what binds) (if (not (list? binds)) (error (string "invalid \"" what "\" declaration"))) (let loop ((b binds) - (vars '()) + (decls '()) (assigns '())) (if (null? b) `(block - ,.(if const? - (map (lambda (x) `(const ,x)) vars) - '()) - ,.(map (lambda (x) `(,what ,x)) vars) - ,.(reverse assigns)) + ,.(reverse decls) + ,.(reverse assigns) + ,.(if (null? assigns) `((null)) '())) (let ((x (car b))) (cond ((or (assignment-like? x) (function-def? x)) - (loop (cdr b) - (append (lhs-decls (assigned-name (cadr x))) vars) + (let ((new-vars (lhs-decls (assigned-name (cadr x))))) + (loop (cdr b) + (append (map (lambda (x) `(,what ,x)) new-vars) decls) (cons `(,(car x) ,(all-decl-vars (cadr x)) ,(caddr x)) - assigns))) + assigns)))) ((and (pair? x) (eq? (car x) '|::|)) (loop (cdr b) - (cons (decl-var x) vars) - (cons `(decl ,@(cdr x)) assigns))) + (cons `(decl ,@(cdr x)) (cons `(,what ,(decl-var x)) decls)) + assigns)) ((symbol? x) - (loop (cdr b) (cons x vars) assigns)) + (loop (cdr b) (cons `(,what, x) decls) assigns)) (else (error (string "invalid syntax in \"" what "\" declaration")))))))) ;; convert (lhss...) = (tuple ...) to assignments, eliminating the tuple -(define (tuple-to-assignments lhss0 x) +(define (tuple-to-assignments lhss0 x wrap) (let loop ((lhss lhss0) (assigned lhss0) (rhss (cdr x)) @@ -1538,7 +1562,7 @@ (loop (cdr lhss) (cons L assigned) (cdr rhss) - (cons (make-assignment L R) stmts) + (cons (wrap (make-assignment L R)) stmts) after (cons R elts))) ((vararg? L) @@ -1549,7 +1573,7 @@ `(block ,@(reverse stmts) (= ,temp (tuple ,@rhss)) ,@(reverse after) - (= ,(cadr L) ,temp) + ,(wrap `(= ,(cadr L) ,temp)) (unnecessary (tuple ,@(reverse elts) (... ,temp))))) (let ((lhss- (reverse lhss)) (rhss- (reverse rhss)) @@ -1581,13 +1605,13 @@ (assigns (if (and (length= lhss- 1) (vararg? (car lhss-))) (begin (set-car! end - (cons `(= ,(cadar lhss-) ,temp) (car end))) + (cons (wrap `(= ,(cadar lhss-) ,temp)) (car end))) assigns) (append (if (> n 0) `(,@assigns (local ,st)) assigns) (destructure- 1 (reverse lhss-) temp - n st end))))) + n st end wrap))))) (loop lhs-tail (append (map (lambda (x) (if (vararg? x) (cadr x) x)) lhss-) assigned) rhs-tail @@ -1600,7 +1624,7 @@ `(block ,@(reverse stmts) ,(make-assignment temp (cadr R)) ,@(reverse after) - (= (tuple ,@lhss) ,temp) + ,(wrap `(= (tuple ,@lhss) ,temp)) (unnecessary (tuple ,@(reverse elts) (... ,temp)))))) (else (let ((temp (if (eventually-call? L) (gensy) (make-ssavalue)))) @@ -1610,11 +1634,11 @@ (if (symbol? temp) (list* (make-assignment temp R) `(local-def ,temp) stmts) (cons (make-assignment temp R) stmts)) - (cons (make-assignment L temp) after) + (cons (wrap (make-assignment L temp)) after) (cons temp elts))))))))) ;; convert (lhss...) = x to tuple indexing -(define (lower-tuple-assignment lhss x) +(define (lower-tuple-assignment lhss x (wrap (lambda (x i) x))) (let ((t (make-ssavalue))) `(block (= ,t ,x) @@ -1629,9 +1653,10 @@ `(block (local-def ,temp) (= ,temp (call (core getfield) ,t ,i)) - (= ,(car lhs) ,temp))) - `(= ,(car lhs) - (call (core getfield) ,t ,i))) + ,(wrap `(= ,(car lhs) ,temp) i))) + (wrap + `(= ,(car lhs) + (call (core getfield) ,t ,i)) i)) (loop (cdr lhs) (+ i 1))))) ,t))) @@ -1794,7 +1819,7 @@ (let ((copied-vars ;; variables not declared `outer` are copied in the innermost loop ;; TODO: maybe filter these to remove vars not assigned in the loop (delete-duplicates - (filter (lambda (x) (not (underscore-symbol? x))) + (filter-not-underscore (apply append (map lhs-vars (filter (lambda (x) (not (outer? x))) (butlast lhss)))))))) @@ -1868,8 +1893,7 @@ ((and flat (pair? expr) (eq? (car expr) 'flatten)) (expand-generator (cadr expr) #t (delete-duplicates (append outervars myvars)))) ((pair? outervars) - `(let (block ,@(map (lambda (v) `(= ,v ,v)) (filter (lambda (x) (not (underscore-symbol? x))) - outervars))) + `(let (block ,@(map (lambda (v) `(= ,v ,v)) (filter-not-underscore outervars))) ,expr)) (else expr)))) `(-> ,argname (block ,@splat ,expr))))))) @@ -2279,10 +2303,14 @@ ;; `end`: car collects statements to be executed afterwards. ;; In general, actual assignments should only happen after ;; the whole iterator is desctructured (https://github.com/JuliaLang/julia/issues/40574) -(define (destructure- i lhss xx n st end) +;; +;; The `wrap` argument is a callback that will be called on all assignments to +;; symbols `lhss`, e.g. to insert a `const` declaration. +(define (destructure- i lhss xx n st end wrap) (if (null? lhss) '() (let* ((lhs (car lhss)) + (wrapfirst (lambda (x i) (if (= i 1) (wrap x) x))) (lhs- (cond ((or (symbol? lhs) (ssavalue? lhs)) lhs) ((vararg? lhs) @@ -2299,30 +2327,30 @@ (error "multiple \"...\" on lhs of assignment")) (if (not (eq? lhs lhs-)) (if (vararg? lhs) - (set-car! end (cons (expand-forms `(= ,(cadr lhs) ,(cadr lhs-))) (car end))) - (set-car! end (cons (expand-forms `(= ,lhs ,lhs-)) (car end))))) + (set-car! end (cons (expand-forms (wrap `(= ,(cadr lhs) ,(cadr lhs-)))) (car end))) + (set-car! end (cons (expand-forms (wrap `(= ,lhs ,lhs-))) (car end))))) (if (vararg? lhs-) (if (= i n) (if (underscore-symbol? (cadr lhs-)) '() (list (expand-forms - `(= ,(cadr lhs-) (call (top rest) ,xx ,@(if (eq? i 1) '() `(,st))))))) + (wrap `(= ,(cadr lhs-) (call (top rest) ,xx ,@(if (eq? i 1) '() `(,st)))))))) (let ((tail (if (eventually-call? lhs) (gensy) (make-ssavalue)))) (cons (expand-forms (lower-tuple-assignment (list (cadr lhs-) tail) - `(call (top split_rest) ,xx ,(- n i) ,@(if (eq? i 1) '() `(,st))))) - (destructure- 1 (cdr lhss) tail (- n i) st end)))) + `(call (top split_rest) ,xx ,(- n i) ,@(if (eq? i 1) '() `(,st))) wrapfirst)) + (destructure- 1 (cdr lhss) tail (- n i) st end wrap)))) (cons (expand-forms (lower-tuple-assignment (if (= i n) (list lhs-) (list lhs- st)) `(call (top indexed_iterate) - ,xx ,i ,@(if (eq? i 1) '() `(,st))))) - (destructure- (+ i 1) (cdr lhss) xx n st end)))))) + ,xx ,i ,@(if (eq? i 1) '() `(,st))) wrapfirst)) + (destructure- (+ i 1) (cdr lhss) xx n st end wrap)))))) -(define (expand-tuple-destruct lhss x) +(define (expand-tuple-destruct lhss x (wrap identity)) (define (sides-match? l r) ;; l and r either have equal lengths, or r has a trailing ... (cond ((null? l) (null? r)) @@ -2335,7 +2363,7 @@ (sides-match? lhss (cdr x))) ;; (a, b, ...) = (x, y, ...) (expand-forms - (tuple-to-assignments lhss x)) + (tuple-to-assignments lhss x wrap)) ;; (a, b, ...) = other (begin ;; like memq, but if lhs is (... sym), check against sym instead @@ -2356,7 +2384,7 @@ `(block ,@(if (> n 0) `((local ,st)) '()) ,@ini - ,@(destructure- 1 lhss xx n st end) + ,@(destructure- 1 lhss xx n st end wrap) ,@(reverse (car end)) (unnecessary ,xx)))))) @@ -2963,6 +2991,10 @@ (set! vars (cons v vars))) (if (not (length= e 2)) (find-assigned-vars- (caddr e))))) + ((assign-const-if-global) + ;; like v = val, except that if `v` turns out global(either + ;; implicitly or by explicit `global`), it gains an implicit `const` + (set! vars (cons (cadr e) vars))) ((=) (let ((v (decl-var (cadr e)))) (find-assigned-vars- (caddr e)) @@ -3044,13 +3076,13 @@ (or (and (memq var (scope:args scope)) 'argument) (and (memq var (scope:locals scope)) 'local) (and (memq var (scope:globals scope)) - (if (and exclude-top-level-globals + (if (and exclude-top-level-globals (null? (lam:args (scope:lam scope))) ;; don't inherit global decls from the outermost scope block ;; in a top-level expression. (or (not (scope:prev scope)) (not (scope:prev (scope:prev scope))))) - 'none 'global)) + 'none 'global)) (and (memq var (scope:sp scope)) 'static-parameter) (var-kind var (scope:prev scope) exclude-top-level-globals)) 'none)) @@ -3084,6 +3116,10 @@ ((eq? (car e) 'global) (check-valid-name (cadr e)) e) + ((eq? (car e) 'assign-const-if-global) + (if (eq? (var-kind (cadr e) scope) 'local) + (if (length= e 2) (null) `(= ,@(cdr e))) + `(const ,@(cdr e)))) ((memq (car e) '(local local-def)) (check-valid-name (cadr e)) ;; remove local decls @@ -3179,7 +3215,6 @@ vars) t) #f))))) - (for-each (lambda (v) (if (or (memq v locals-def) (memq v local-decls)) (error (string "variable \"" v "\" declared both local and global"))) @@ -3434,14 +3469,14 @@ f(x) = yt(x) (s (make-ssavalue))) `((thunk ,(linearize `(lambda () (() () 0 ()) - (block (global ,name) (const ,name) + (block (global ,name) ,@(map (lambda (p n) `(= ,p (call (core TypeVar) ',n (core Any)))) P names) (= ,s (call (core _structtype) (thismodule) (inert ,name) (call (core svec) ,@P) (call (core svec) ,@(map quotify fields)) (call (core svec)) (false) ,(length fields))) (call (core _setsuper!) ,s ,super) - (= (globalref (thismodule) ,name) ,s) + (const (globalref (thismodule) ,name) ,s) (call (core _typebody!) ,s (call (core svec) ,@types)) (return (null))))))))) @@ -3449,13 +3484,13 @@ f(x) = yt(x) (let ((s (make-ssavalue))) `((thunk ,(linearize `(lambda () (() () 0 ()) - (block (global ,name) (const ,name) + (block (global ,name) (= ,s (call (core _structtype) (thismodule) (inert ,name) (call (core svec)) (call (core svec) ,@(map quotify fields)) (call (core svec)) (false) ,(length fields))) (call (core _setsuper!) ,s ,super) - (= (globalref (thismodule) ,name) ,s) + (const (globalref (thismodule) ,name) ,s) (call (core _typebody!) ,s (call (core svec) ,@(map (lambda (v) '(core Box)) fields))) (return (null))))))))) @@ -3711,7 +3746,7 @@ f(x) = yt(x) (Set '(quote top core lineinfo line inert local-def unnecessary copyast meta inbounds boundscheck loopinfo decl aliasscope popaliasscope thunk with-static-parameters toplevel-only - global globalref const-if-global thismodule + global globalref assign-const-if-global thismodule const atomic null true false ssavalue isdefined toplevel module lambda error gc_preserve_begin gc_preserve_end import using export public inline noinline purity))) @@ -3962,10 +3997,6 @@ f(x) = yt(x) (put! globals (binding-to-globalref (cadr e)) #f) e) ((atomic) e) - ((const-if-global) - (if (local-in? (cadr e) lam locals) - '(null) - `(const ,(cadr e)))) ((isdefined) ;; convert isdefined expr to function for closure converted variables (let* ((sym (cadr e)) (vi (and (symbol? sym) (get locals sym #f))) @@ -4234,8 +4265,8 @@ f(x) = yt(x) (put! globals ref #t) `(block (toplevel-only set_binding_type! ,(cadr e)) - (global ,ref) - (call (core set_binding_type!) ,(cadr ref) (inert ,(caddr ref)) ,(caddr e)))) + (globaldecl ,ref ,(caddr e)) + (null))) `(call (core typeassert) ,@(cdr e)))) fname lam namemap defined toplevel interp opaq globals locals)))) ;; `with-static-parameters` expressions can be removed now; used only by analyze-vars @@ -4822,6 +4853,12 @@ f(x) = yt(x) ((global) ; keep global declarations as statements (if value (error "misplaced \"global\" declaration")) (emit e)) + ((globaldecl) + (if value (error "misplaced \"global\" declaration")) + (if (atom? (caddr e)) (emit e) + (let ((rr (make-ssavalue))) + (emit `(= ,rr ,(caddr e))) + (emit `(globaldecl ,(cadr e) ,rr))))) ((local-def) #f) ((local) #f) ((moved-local) diff --git a/src/julia.h b/src/julia.h index 5380128d06202..9ade63660b482 100644 --- a/src/julia.h +++ b/src/julia.h @@ -1960,6 +1960,7 @@ JL_DLLEXPORT jl_value_t *jl_checked_replace(jl_binding_t *b, jl_module_t *mod, j JL_DLLEXPORT jl_value_t *jl_checked_modify(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *op, jl_value_t *rhs); JL_DLLEXPORT jl_value_t *jl_checked_assignonce(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *rhs JL_MAYBE_UNROOTED); JL_DLLEXPORT void jl_declare_constant(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var); +JL_DLLEXPORT void jl_declare_constant_val(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *val); JL_DLLEXPORT void jl_module_using(jl_module_t *to, jl_module_t *from); JL_DLLEXPORT void jl_module_use(jl_module_t *to, jl_module_t *from, jl_sym_t *s); JL_DLLEXPORT void jl_module_use_as(jl_module_t *to, jl_module_t *from, jl_sym_t *s, jl_sym_t *asname); diff --git a/src/julia_internal.h b/src/julia_internal.h index ae6c97d9c9a28..74c7889094f16 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -799,7 +799,10 @@ jl_array_t *jl_get_loaded_modules(void); JL_DLLEXPORT int jl_datatype_isinlinealloc(jl_datatype_t *ty, int pointerfree); int jl_type_equality_is_identity(jl_value_t *t1, jl_value_t *t2) JL_NOTSAFEPOINT; +JL_DLLEXPORT void jl_eval_const_decl(jl_module_t *m, jl_value_t *arg, jl_value_t *val); +void jl_binding_set_type(jl_binding_t *b, jl_value_t *ty, int error); void jl_eval_global_expr(jl_module_t *m, jl_expr_t *ex, int set_type); +JL_DLLEXPORT void jl_declare_global(jl_module_t *m, jl_value_t *arg, jl_value_t *set_type); JL_DLLEXPORT jl_value_t *jl_toplevel_eval_flex(jl_module_t *m, jl_value_t *e, int fast, int expanded, const char **toplevel_filename, int *toplevel_lineno); jl_value_t *jl_eval_global_var(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *e); @@ -1616,6 +1619,7 @@ extern JL_DLLEXPORT jl_sym_t *jl_thunk_sym; extern JL_DLLEXPORT jl_sym_t *jl_foreigncall_sym; extern JL_DLLEXPORT jl_sym_t *jl_as_sym; extern JL_DLLEXPORT jl_sym_t *jl_global_sym; +extern JL_DLLEXPORT jl_sym_t *jl_globaldecl_sym; extern JL_DLLEXPORT jl_sym_t *jl_local_sym; extern JL_DLLEXPORT jl_sym_t *jl_list_sym; extern JL_DLLEXPORT jl_sym_t *jl_dot_sym; diff --git a/src/staticdata.c b/src/staticdata.c index ea2fac6c0fce6..cf3a2a5b2b931 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -494,7 +494,7 @@ static const jl_fptr_args_t id_to_fptrs[] = { &jl_f_applicable, &jl_f_invoke, &jl_f_sizeof, &jl_f__expr, &jl_f__typevar, &jl_f_ifelse, &jl_f__structtype, &jl_f__abstracttype, &jl_f__primitivetype, &jl_f__typebody, &jl_f__setsuper, &jl_f__equiv_typedef, &jl_f_get_binding_type, - &jl_f_set_binding_type, &jl_f_opaque_closure_call, &jl_f_donotdelete, &jl_f_compilerbarrier, + &jl_f_opaque_closure_call, &jl_f_donotdelete, &jl_f_compilerbarrier, &jl_f_getglobal, &jl_f_setglobal, &jl_f_swapglobal, &jl_f_modifyglobal, &jl_f_replaceglobal, &jl_f_setglobalonce, &jl_f_finalizer, &jl_f__compute_sparams, &jl_f__svec_ref, &jl_f_current_scope, diff --git a/src/toplevel.c b/src/toplevel.c index 22ef91d55fb01..a9d11cb2d643d 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -308,30 +308,46 @@ static jl_value_t *jl_eval_dot_expr(jl_module_t *m, jl_value_t *x, jl_value_t *f return args[0]; } -void jl_eval_global_expr(jl_module_t *m, jl_expr_t *ex, int set_type) { +void jl_binding_set_type(jl_binding_t *b, jl_value_t *ty, int error) +{ + jl_value_t *old_ty = NULL; + if (jl_atomic_cmpswap_relaxed(&b->ty, &old_ty, ty)) { + jl_gc_wb(b, ty); + } + else if (error && !jl_types_equal(ty, old_ty)) { + jl_errorf("cannot set type for global %s.%s. It already has a value or is already set to a different type.", + jl_symbol_name(jl_globalref_mod(b->globalref)->name), jl_symbol_name(jl_globalref_name(b->globalref))); + } +} + +void jl_declare_global(jl_module_t *m, jl_value_t *arg, jl_value_t *set_type) { // create uninitialized mutable binding for "global x" decl sometimes or probably + jl_module_t *gm; + jl_sym_t *gs; + assert(!jl_is_expr(arg)); // Should have been resolved before this + if (jl_is_globalref(arg)) { + gm = jl_globalref_mod(arg); + gs = jl_globalref_name(arg); + } + else { + assert(jl_is_symbol(arg)); + gm = m; + gs = (jl_sym_t*)arg; + } + if (!jl_binding_resolved_p(gm, gs) || set_type) { + jl_binding_t *b = jl_get_binding_wr(gm, gs, 1); + if (set_type) { + jl_binding_set_type(b, set_type, 1); + } + } +} + +void jl_eval_global_expr(jl_module_t *m, jl_expr_t *ex, int set_type) +{ size_t i, l = jl_array_nrows(ex->args); for (i = 0; i < l; i++) { jl_value_t *arg = jl_exprarg(ex, i); - jl_module_t *gm; - jl_sym_t *gs; - if (jl_is_globalref(arg)) { - gm = jl_globalref_mod(arg); - gs = jl_globalref_name(arg); - } - else { - assert(jl_is_symbol(arg)); - gm = m; - gs = (jl_sym_t*)arg; - } - if (!jl_binding_resolved_p(gm, gs)) { - jl_binding_t *b = jl_get_binding_wr(gm, gs, 1); - if (set_type) { - jl_value_t *old_ty = NULL; - // maybe set the type too, perhaps - jl_atomic_cmpswap_relaxed(&b->ty, &old_ty, (jl_value_t*)jl_any_type); - } - } + jl_declare_global(m, arg, NULL); } } @@ -568,6 +584,7 @@ int jl_is_toplevel_only_expr(jl_value_t *e) JL_NOTSAFEPOINT ((jl_expr_t*)e)->head == jl_public_sym || ((jl_expr_t*)e)->head == jl_thunk_sym || ((jl_expr_t*)e)->head == jl_global_sym || + ((jl_expr_t*)e)->head == jl_globaldecl_sym || ((jl_expr_t*)e)->head == jl_const_sym || ((jl_expr_t*)e)->head == jl_toplevel_sym || ((jl_expr_t*)e)->head == jl_error_sym || @@ -704,6 +721,33 @@ static void jl_eval_errorf(jl_module_t *m, const char *filename, int lineno, con JL_GC_POP(); } +JL_DLLEXPORT void jl_declare_constant_val(jl_binding_t *b, jl_module_t *gm, jl_sym_t *gs, jl_value_t *val) +{ + jl_declare_constant(b, gm, gs); + jl_checked_assignment(b, gm, gs, val); +} + +JL_DLLEXPORT void jl_eval_const_decl(jl_module_t *m, jl_value_t *arg, jl_value_t *val) +{ + jl_module_t *gm; + jl_sym_t *gs; + if (jl_is_globalref(arg)) { + gm = jl_globalref_mod(arg); + gs = jl_globalref_name(arg); + } + else { + assert(jl_is_symbol(arg)); + gm = m; + gs = (jl_sym_t*)arg; + } + jl_binding_t *b = jl_get_binding_wr(gm, gs, 1); + if (val) { + jl_declare_constant_val(b, gm, gs, val); + } else { + jl_declare_constant(b, gm, gs); + } +} + JL_DLLEXPORT jl_value_t *jl_toplevel_eval_flex(jl_module_t *JL_NONNULL m, jl_value_t *e, int fast, int expanded, const char **toplevel_filename, int *toplevel_lineno) { jl_task_t *ct = jl_current_task; @@ -748,7 +792,8 @@ JL_DLLEXPORT jl_value_t *jl_toplevel_eval_flex(jl_module_t *JL_NONNULL m, jl_val jl_method_instance_t *mfunc = NULL; jl_code_info_t *thk = NULL; - JL_GC_PUSH3(&mfunc, &thk, &ex); + jl_value_t *root = NULL; + JL_GC_PUSH4(&mfunc, &thk, &ex, &root); size_t last_age = ct->world_age; if (!expanded && jl_needs_lowering(e)) { @@ -880,25 +925,16 @@ JL_DLLEXPORT jl_value_t *jl_toplevel_eval_flex(jl_module_t *JL_NONNULL m, jl_val return jl_nothing; } else if (head == jl_global_sym) { - jl_eval_global_expr(m, ex, 0); + size_t i, l = jl_array_nrows(ex->args); + for (i = 0; i < l; i++) { + jl_value_t *arg = jl_exprarg(ex, i); + jl_declare_global(m, arg, NULL); + } JL_GC_POP(); return jl_nothing; } else if (head == jl_const_sym) { - jl_sym_t *arg = (jl_sym_t*)jl_exprarg(ex, 0); - jl_module_t *gm; - jl_sym_t *gs; - if (jl_is_globalref(arg)) { - gm = jl_globalref_mod(arg); - gs = jl_globalref_name(arg); - } - else { - assert(jl_is_symbol(arg)); - gm = m; - gs = (jl_sym_t*)arg; - } - jl_binding_t *b = jl_get_binding_wr(gm, gs, 1); - jl_declare_constant(b, gm, gs); + jl_eval_const_decl(m, jl_exprarg(ex, 0), NULL); JL_GC_POP(); return jl_nothing; } diff --git a/test/core.jl b/test/core.jl index 2b496fbf64f12..462c24fa04ab5 100644 --- a/test/core.jl +++ b/test/core.jl @@ -8146,13 +8146,13 @@ end let M = @__MODULE__ Core.eval(M, :(global a_typed_global)) - @test Core.set_binding_type!(M, :a_typed_global, Tuple{Union{Integer,Nothing}}) === nothing + @test Core.eval(M, :(global a_typed_global::$(Tuple{Union{Integer,Nothing}}))) === nothing @test Core.get_binding_type(M, :a_typed_global) === Tuple{Union{Integer,Nothing}} - @test Core.set_binding_type!(M, :a_typed_global, Tuple{Union{Integer,Nothing}}) === nothing - @test Core.set_binding_type!(M, :a_typed_global, Union{Tuple{Integer},Tuple{Nothing}}) === nothing + @test Core.eval(M, :(global a_typed_global::$(Tuple{Union{Integer,Nothing}}))) === nothing + @test Core.eval(M, :(global a_typed_global::$(Union{Tuple{Integer},Tuple{Nothing}}))) === nothing @test_throws(ErrorException("cannot set type for global $(nameof(M)).a_typed_global. It already has a value or is already set to a different type."), - Core.set_binding_type!(M, :a_typed_global, Union{Nothing,Tuple{Union{Integer,Nothing}}})) - @test Core.set_binding_type!(M, :a_typed_global) === nothing + Core.eval(M, :(global a_typed_global::$(Union{Nothing,Tuple{Union{Integer,Nothing}}})))) + @test Core.eval(M, :(global a_typed_global)) === nothing @test Core.get_binding_type(M, :a_typed_global) === Tuple{Union{Integer,Nothing}} end diff --git a/test/precompile.jl b/test/precompile.jl index ce606d166f695..266ca9df3e2e1 100644 --- a/test/precompile.jl +++ b/test/precompile.jl @@ -218,9 +218,9 @@ precompile_test_harness(false) do dir gnc() = overridenc(1.0) Test.@test 1 < gnc() < 5 # compile this - const abigfloat_f() = big"12.34" + abigfloat_f() = big"12.34" const abigfloat_x = big"43.21" - const abigint_f() = big"123" + abigint_f() = big"123" const abigint_x = big"124" # issue #51111 @@ -1907,7 +1907,7 @@ precompile_test_harness("Issue #50538") do load_path ex end const newtype = try - Core.set_binding_type!(Base, :newglobal) + Core.eval(Base, :(global newglobal::Any)) catch ex ex isa ErrorException || rethrow() ex @@ -1918,10 +1918,10 @@ precompile_test_harness("Issue #50538") do load_path ji, ofile = Base.compilecache(Base.PkgId("I50538")) @eval using I50538 @test I50538.newglobal.msg == "Creating a new global in closed module `Base` (`newglobal`) breaks incremental compilation because the side effects will not be permanent." - @test I50538.newtype.msg == "Creating a new global in closed module `Base` (`newglobal`) breaks incremental compilation because the side effects will not be permanent." + @test I50538.newtype.msg == "Evaluation into the closed module `Base` breaks incremental compilation because the side effects will not be permanent. This is likely due to some other module mutating `Base` with `eval` during precompilation - don't do this." @test_throws(ErrorException("cannot set type for global I50538.undefglobal. It already has a value or is already set to a different type."), - Core.set_binding_type!(I50538, :undefglobal, Int)) - Core.set_binding_type!(I50538, :undefglobal, Any) + Core.eval(I50538, :(global undefglobal::Int))) + Core.eval(I50538, :(global undefglobal::Any)) @test Core.get_binding_type(I50538, :undefglobal) === Any @test !isdefined(I50538, :undefglobal) end diff --git a/test/syntax.jl b/test/syntax.jl index 3c0b8db78a560..f92eb071415cc 100644 --- a/test/syntax.jl +++ b/test/syntax.jl @@ -3747,3 +3747,101 @@ b54805 = 2 end using .Export54805 @test b54805 == 2 + +# F{T} = ... has special syntax semantics, not found anywhere else in the language +# that make `F` `const` iff an assignment to `F` is global in the relevant scope. +# We implicitly test this elsewhere, but there's some tricky interactions with +# explicit declarations that we test here. +module ImplicitCurlies + using ..Test + let + ImplicitCurly1{T} = Ref{T} + end + @test !@isdefined(ImplicitCurly1) + let + global ImplicitCurly2 + ImplicitCurly2{T} = Ref{T} + end + @test @isdefined(ImplicitCurly2) && isconst(@__MODULE__, :ImplicitCurly2) + begin + ImplicitCurly3{T} = Ref{T} + end + @test @isdefined(ImplicitCurly3) && isconst(@__MODULE__, :ImplicitCurly3) + begin + local ImplicitCurly4 + ImplicitCurly4{T} = Ref{T} + end + @test !@isdefined(ImplicitCurly4) + @test_throws "syntax: `global const` declaration not allowed inside function" Core.eval(@__MODULE__, :(function implicit5() + global ImplicitCurly5 + ImplicitCurly5{T} = Ref{T} + end)) + @test !@isdefined(ImplicitCurly5) + function implicit6() + ImplicitCurly6{T} = Ref{T} + return ImplicitCurly6 + end + @test !@isdefined(ImplicitCurly6) + # Check return value of assignment expr + @test isa((const ImplicitCurly7{T} = Ref{T}), UnionAll) + @test isa(begin; ImplicitCurly8{T} = Ref{T}; end, UnionAll) +end + +# `const` does not distribute over assignments +const aconstassign = bconstassign = 2 +@test isconst(@__MODULE__, :aconstassign) +@test !isconst(@__MODULE__, :bconstassign) +@test aconstassign == bconstassign + +const afunc_constassign() = bfunc_constassign() = 2 +@test afunc_constassign()() == 2 +@test !@isdefined(bfunc_constassign) + +# `const` RHS is regular toplevel scope (not `let`) +const arhs_toplevel = begin + athis_should_be_a_global = 1 + 2 +end +@test isconst(@__MODULE__, :arhs_toplevel) +@test !isconst(@__MODULE__, :athis_should_be_a_global) +@test arhs_toplevel == 2 +@test athis_should_be_a_global == 1 + +# `const` is permitted before function assignment for legacy reasons +const fconst_assign() = 1 +const (gconst_assign(), hconst_assign()) = (2, 3) +@test (fconst_assign(), gconst_assign(), hconst_assign()) == (1, 2, 3) +@test isconst(@__MODULE__, :fconst_assign) +@test isconst(@__MODULE__, :gconst_assign) +@test isconst(@__MODULE__, :hconst_assign) + +# `const` assignment to `_` drops the assignment effect, +# and the conversion, but not the rhs. +struct CantConvert; end +Base.convert(::Type{CantConvert}, x) = error() +@test (const _::CantConvert = 1) == 1 +@test !isconst(@__MODULE__, :_) +@test_throws ErrorException("expected") (const _ = error("expected")) + +# Issue #54787 +const (destruct_const54787...,) = (1,2,3) +@test destruct_const54787 == (1,2,3) +@test isconst(@__MODULE__, :destruct_const54787) +const a54787, b54787, c54787 = destruct_const54787 +@test (a54787, b54787, c54787) == (1,2,3) +@test isconst(@__MODULE__, :a54787) +@test isconst(@__MODULE__, :b54787) +@test isconst(@__MODULE__, :c54787) + +# Same number of statements on lhs and rhs, but non-atom +const c54787_1,c54787_2 = 1,(2*1) +@test isconst(@__MODULE__, :c54787_1) +@test isconst(@__MODULE__, :c54787_2) +@test c54787_1 == 1 +@test c54787_2 == 2 + +# Methods can be added to any singleton not just generic functions +struct SingletonMaker; end +const no_really_this_is_a_function_i_promise = Val{SingletonMaker()}() +no_really_this_is_a_function_i_promise(a) = 2 + a +@test Val{SingletonMaker()}()(2) == 4