Skip to content

Commit ee478df

Browse files
committed
support type annotations on global variables
This is an initial proposal for supporting typed globals based on #43455. The syntax for this is either `global x::T` or just doing `x::T = 1` in global scope. It is even supported to add these annotations to globals from inside functions. The type declaration will then be applied when the method is defined and inside the function body conversion to the specified type will happen automatically, similar to type annotations for local variables. This conversion will not be applied if the assignment is not done inside the same scope of the type annotation however. This could potentially be supported as well, but the problem is that this would mean any assignment to a global variable - typed or not - would need to go through a call to `convert` first, since lowering can't know if a given binding already has a type annotation. This probably wouldn't be a good thing for latency and sysimage size and would add an invalidation risk. It is allowed to refine a type annotation, but there will be a warning. Widening the type or changing it to any other type that is not a subtype of the previous one is an error, since that could cause cached compiled code to become invalid. replaces #43455 closes #964
1 parent f819538 commit ee478df

8 files changed

+186
-55
lines changed

base/compiler/abstractinterpretation.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -1805,7 +1805,7 @@ function abstract_eval_global(M::Module, s::Symbol)
18051805
if isdefined(M,s) && isconst(M,s)
18061806
return Const(getfield(M,s))
18071807
end
1808-
return Any
1808+
return ccall(:jl_get_binding_type, Any, (Any, Any), M, s)
18091809
end
18101810

18111811
function abstract_eval_ssavalue(s::SSAValue, src::CodeInfo)

src/builtin_proto.h

+1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ JL_CALLABLE(jl_f__abstracttype);
6565
JL_CALLABLE(jl_f__primitivetype);
6666
JL_CALLABLE(jl_f__setsuper);
6767
JL_CALLABLE(jl_f__equiv_typedef);
68+
JL_CALLABLE(jl_f__set_typeof);
6869

6970
#ifdef __cplusplus
7071
}

src/builtins.c

+31
Original file line numberDiff line numberDiff line change
@@ -1655,6 +1655,36 @@ JL_CALLABLE(jl_f__equiv_typedef)
16551655
return equiv_type(args[0], args[1]) ? jl_true : jl_false;
16561656
}
16571657

1658+
JL_CALLABLE(jl_f__set_typeof)
1659+
{
1660+
JL_NARGS(_set_typeof!, 3, 3);
1661+
JL_TYPECHK(_set_typeof!, module, args[0]);
1662+
JL_TYPECHK(_set_typeof!, symbol, args[1]);
1663+
jl_value_t *ty = args[2];
1664+
JL_TYPECHK(_set_typeof!, type, ty);
1665+
jl_binding_t *b = jl_get_binding_wr((jl_module_t*)args[0], (jl_sym_t*)args[1], 1);
1666+
if (b->constp && ty != (jl_value_t*)jl_any_type) {
1667+
jl_errorf("cannot set type for constant %s", jl_symbol_name(b->name));
1668+
}
1669+
if (b->value && !jl_isa(b->value, ty)) {
1670+
jl_errorf("cannot set type for global %s. It already has a value of a different type.",
1671+
jl_symbol_name(b->name));
1672+
}
1673+
if (b->ty && ty != b->ty) {
1674+
if (jl_subtype(ty, b->ty)) {
1675+
jl_safe_printf("WARNING: redefinition of type of %s. This may fail, cause incorrect answers, or produce other errors.\n",
1676+
jl_symbol_name(b->name));
1677+
}
1678+
else {
1679+
jl_errorf("invalid redefinition of type of %s", jl_symbol_name(b->name));
1680+
}
1681+
}
1682+
if (ty != (jl_value_t*)jl_any_type) {
1683+
b->ty = ty;
1684+
}
1685+
return jl_nothing;
1686+
}
1687+
16581688
// IntrinsicFunctions ---------------------------------------------------------
16591689

16601690
static void (*runtime_fp[num_intrinsics])(void);
@@ -1833,6 +1863,7 @@ void jl_init_primitives(void) JL_GC_DISABLED
18331863
add_builtin_func("_setsuper!", jl_f__setsuper);
18341864
jl_builtin__typebody = add_builtin_func("_typebody!", jl_f__typebody);
18351865
add_builtin_func("_equiv_typedef", jl_f__equiv_typedef);
1866+
add_builtin_func("_set_typeof!", jl_f__set_typeof);
18361867

18371868
// builtin types
18381869
add_builtin("Any", (jl_value_t*)jl_any_type);

src/julia-syntax.scm

+72-47
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,12 @@
260260
(or (overlay? e)
261261
(sym-ref? e)))
262262

263+
(define (binding-to-globalref e)
264+
(and (nodot-sym-ref? e)
265+
(let ((mod (if (globalref? e) (cadr e) '(thismodule)))
266+
(sym (if (symbol? e) e (last e))))
267+
`(globalref ,mod ,sym))))
268+
263269
;; convert final (... x) to (curly Vararg x)
264270
(define (dots->vararg a)
265271
(if (null? a) a
@@ -3383,7 +3389,7 @@ f(x) = yt(x)
33833389
;; declared types.
33843390
;; when doing this, the original value needs to be preserved, to
33853391
;; ensure the expression `a=b` always returns exactly `b`.
3386-
(define (convert-assignment var rhs0 fname lam interp opaq)
3392+
(define (convert-assignment var rhs0 fname lam interp opaq globals)
33873393
(cond
33883394
((symbol? var)
33893395
(let* ((vi (assq var (car (lam:vinfo lam))))
@@ -3393,32 +3399,39 @@ f(x) = yt(x)
33933399
'(core Any)))
33943400
(closed (and cv (vinfo:asgn cv) (vinfo:capt cv)))
33953401
(capt (and vi (vinfo:asgn vi) (vinfo:capt vi))))
3396-
(if (and (not closed) (not capt) (equal? vt '(core Any)))
3397-
`(= ,var ,rhs0)
3398-
(let* ((rhs1 (if (or (simple-atom? rhs0)
3399-
(equal? rhs0 '(the_exception)))
3400-
rhs0
3401-
(make-ssavalue)))
3402-
(rhs (if (equal? vt '(core Any))
3403-
rhs1
3404-
(convert-for-type-decl rhs1 (cl-convert vt fname lam #f #f #f interp opaq))))
3405-
(ex (cond (closed `(call (core setfield!)
3406-
,(if interp
3407-
`($ ,var)
3408-
(capt-var-access var fname opaq))
3409-
(inert contents)
3410-
,rhs))
3411-
(capt `(call (core setfield!) ,var (inert contents) ,rhs))
3412-
(else `(= ,var ,rhs)))))
3413-
(if (eq? rhs1 rhs0)
3414-
`(block ,ex ,rhs0)
3415-
`(block (= ,rhs1 ,rhs0)
3416-
,ex
3417-
,rhs1))))))
3418-
((and (pair? var) (or (eq? (car var) 'outerref)
3419-
(eq? (car var) 'globalref)))
3420-
3421-
`(= ,var ,rhs0))
3402+
(let* ((rhs1 (if (or (simple-atom? rhs0)
3403+
(equal? rhs0 '(the_exception)))
3404+
rhs0
3405+
(make-ssavalue)))
3406+
(rhs (if (equal? vt '(core Any))
3407+
(if (and (not closed) (not capt))
3408+
(convert-for-type-decl rhs1 (get globals (binding-to-globalref var) '(core Any)))
3409+
rhs1)
3410+
(convert-for-type-decl rhs1 (cl-convert vt fname lam #f #f #f interp opaq globals))))
3411+
(ex (cond (closed `(call (core setfield!)
3412+
,(if interp
3413+
`($ ,var)
3414+
(capt-var-access var fname opaq))
3415+
(inert contents)
3416+
,rhs))
3417+
(capt `(call (core setfield!) ,var (inert contents) ,rhs))
3418+
(else `(= ,var ,rhs)))))
3419+
(if (eq? rhs1 rhs0)
3420+
`(block ,ex ,rhs0)
3421+
`(block (= ,rhs1 ,rhs0)
3422+
,ex
3423+
,rhs1)))))
3424+
((or (outerref? var) (globalref? var))
3425+
(let* ((rhs1 (if (or (simple-atom? rhs0)
3426+
(equal? rhs0 '(the_exception)))
3427+
rhs0
3428+
(make-ssavalue)))
3429+
(ex `(= ,var ,(convert-for-type-decl rhs1 (get globals (binding-to-globalref var) '(core Any))))))
3430+
(if (eq? rhs1 rhs0)
3431+
`(block ,ex ,rhs0)
3432+
`(block (= ,rhs1 ,rhs0)
3433+
,ex
3434+
,rhs1))))
34223435
((ssavalue? var)
34233436
`(= ,var ,rhs0))
34243437
(else
@@ -3678,17 +3691,17 @@ f(x) = yt(x)
36783691
(define (toplevel-preserving? e)
36793692
(and (pair? e) (memq (car e) '(if elseif block trycatch tryfinally trycatchelse))))
36803693

3681-
(define (map-cl-convert exprs fname lam namemap defined toplevel interp opaq)
3694+
(define (map-cl-convert exprs fname lam namemap defined toplevel interp opaq (globals (table)))
36823695
(if toplevel
36833696
(map (lambda (x)
36843697
(let ((tl (lift-toplevel (cl-convert x fname lam namemap defined
36853698
(and toplevel (toplevel-preserving? x))
3686-
interp opaq))))
3699+
interp opaq globals))))
36873700
(if (null? (cdr tl))
36883701
(car tl)
36893702
`(block ,@(cdr tl) ,(car tl)))))
36903703
exprs)
3691-
(map (lambda (x) (cl-convert x fname lam namemap defined #f interp opaq)) exprs)))
3704+
(map (lambda (x) (cl-convert x fname lam namemap defined #f interp opaq globals)) exprs)))
36923705

36933706
(define (prepare-lambda! lam)
36943707
;; mark all non-arguments as assigned, since locals that are never assigned
@@ -3697,11 +3710,11 @@ f(x) = yt(x)
36973710
(list-tail (car (lam:vinfo lam)) (length (lam:args lam))))
36983711
(lambda-optimize-vars! lam))
36993712

3700-
(define (cl-convert e fname lam namemap defined toplevel interp opaq)
3713+
(define (cl-convert e fname lam namemap defined toplevel interp opaq (globals (table)))
37013714
(if (and (not lam)
37023715
(not (and (pair? e) (memq (car e) '(lambda method macro opaque_closure)))))
37033716
(if (atom? e) e
3704-
(cons (car e) (map-cl-convert (cdr e) fname lam namemap defined toplevel interp opaq)))
3717+
(cons (car e) (map-cl-convert (cdr e) fname lam namemap defined toplevel interp opaq globals)))
37053718
(cond
37063719
((symbol? e)
37073720
(define (new-undef-var name)
@@ -3720,7 +3733,7 @@ f(x) = yt(x)
37203733
(val (if (equal? typ '(core Any))
37213734
val
37223735
`(call (core typeassert) ,val
3723-
,(cl-convert typ fname lam namemap defined toplevel interp opaq)))))
3736+
,(cl-convert typ fname lam namemap defined toplevel interp opaq globals)))))
37243737
`(block
37253738
,@(if (eq? box access) '() `((= ,access ,box)))
37263739
,undefcheck
@@ -3752,8 +3765,8 @@ f(x) = yt(x)
37523765
e)
37533766
((=)
37543767
(let ((var (cadr e))
3755-
(rhs (cl-convert (caddr e) fname lam namemap defined toplevel interp opaq)))
3756-
(convert-assignment var rhs fname lam interp opaq)))
3768+
(rhs (cl-convert (caddr e) fname lam namemap defined toplevel interp opaq globals)))
3769+
(convert-assignment var rhs fname lam interp opaq globals)))
37573770
((local-def) ;; make new Box for local declaration of defined variable
37583771
(let ((vi (assq (cadr e) (car (lam:vinfo lam)))))
37593772
(if (and vi (vinfo:asgn vi) (vinfo:capt vi))
@@ -3819,7 +3832,7 @@ f(x) = yt(x)
38193832
(sp-inits (if (or short (not (eq? (car sig) 'block)))
38203833
'()
38213834
(map-cl-convert (butlast (cdr sig))
3822-
fname lam namemap defined toplevel interp opaq)))
3835+
fname lam namemap defined toplevel interp opaq globals)))
38233836
(sig (and sig (if (eq? (car sig) 'block)
38243837
(last sig)
38253838
sig))))
@@ -3846,7 +3859,7 @@ f(x) = yt(x)
38463859
;; anonymous functions with keyword args generate global
38473860
;; functions that refer to the type of a local function
38483861
(rename-sig-types sig namemap)
3849-
fname lam namemap defined toplevel interp opaq)
3862+
fname lam namemap defined toplevel interp opaq globals)
38503863
,(let ((body (add-box-inits-to-body
38513864
lam2
38523865
(cl-convert (cadddr lam2) 'anon lam2 (table) (table) #f interp opaq))))
@@ -3860,7 +3873,7 @@ f(x) = yt(x)
38603873
(newlam (compact-and-renumber (linearize (car exprs)) 'none 0)))
38613874
`(toplevel-butfirst
38623875
(block ,@sp-inits
3863-
(method ,name ,(cl-convert sig fname lam namemap defined toplevel interp opaq)
3876+
(method ,name ,(cl-convert sig fname lam namemap defined toplevel interp opaq globals)
38643877
,(julia-bq-macro newlam)))
38653878
,@top-stmts))))
38663879

@@ -3963,7 +3976,7 @@ f(x) = yt(x)
39633976
(append (map (lambda (gs tvar)
39643977
(make-assignment gs `(call (core TypeVar) ',tvar (core Any))))
39653978
closure-param-syms closure-param-names)
3966-
`((method #f ,(cl-convert arg-defs fname lam namemap defined toplevel interp opaq)
3979+
`((method #f ,(cl-convert arg-defs fname lam namemap defined toplevel interp opaq globals)
39673980
,(convert-lambda lam2
39683981
(if iskw
39693982
(caddr (lam:args lam2))
@@ -4002,7 +4015,7 @@ f(x) = yt(x)
40024015
(begin
40034016
(put! defined name #t)
40044017
`(toplevel-butfirst
4005-
,(convert-assignment name mk-closure fname lam interp opaq)
4018+
,(convert-assignment name mk-closure fname lam interp opaq globals)
40064019
,@typedef
40074020
,@(map (lambda (v) `(moved-local ,v)) moved-vars)
40084021
,@sp-inits
@@ -4015,30 +4028,42 @@ f(x) = yt(x)
40154028
(table)
40164029
(table)
40174030
(null? (cadr e)) ;; only toplevel thunks have 0 args
4018-
interp opaq)))
4031+
interp opaq globals)))
40194032
`(lambda ,(cadr e)
40204033
(,(clear-capture-bits (car (lam:vinfo e)))
40214034
() ,@(cddr (lam:vinfo e)))
40224035
(block ,@body))))
40234036
;; remaining `::` expressions are type assertions
40244037
((|::|)
4025-
(cl-convert `(call (core typeassert) ,@(cdr e)) fname lam namemap defined toplevel interp opaq))
4038+
(cl-convert `(call (core typeassert) ,@(cdr e)) fname lam namemap defined toplevel interp opaq globals))
40264039
;; remaining `decl` expressions are only type assertions if the
40274040
;; argument is global or a non-symbol.
40284041
((decl)
40294042
(cond ((and (symbol? (cadr e))
40304043
(local-in? (cadr e) lam))
40314044
'(null))
40324045
(else
4033-
(if (or (symbol? (cadr e)) (and (pair? (cadr e)) (eq? (caadr e) 'outerref)))
4034-
(error "type declarations on global variables are not yet supported"))
4035-
(cl-convert `(call (core typeassert) ,@(cdr e)) fname lam namemap defined toplevel interp opaq))))
4046+
(cl-convert
4047+
(let ((ref (binding-to-globalref (cadr e))))
4048+
(and ref (get globals ref #f)
4049+
(error (string "multiple type annotations for global \""
4050+
(deparse (cadr e)) "\".")))
4051+
(if ref
4052+
(let* ((rhs0 (caddr e))
4053+
(temp (and (not (effect-free? rhs0)) (make-ssavalue)))
4054+
(rhs (or temp rhs0)))
4055+
(put! globals ref rhs)
4056+
`(toplevel-butfirst
4057+
,(if temp `(block (= ,temp ,rhs0) (null)) '(null))
4058+
(call (core _set_typeof!) ,(cadr ref) (inert ,(caddr ref)) ,(caddr e))))
4059+
`(call (core typeassert) ,@(cdr e))))
4060+
fname lam namemap defined toplevel interp opaq globals))))
40364061
;; `with-static-parameters` expressions can be removed now; used only by analyze-vars
40374062
((with-static-parameters)
4038-
(cl-convert (cadr e) fname lam namemap defined toplevel interp opaq))
4063+
(cl-convert (cadr e) fname lam namemap defined toplevel interp opaq globals))
40394064
(else
40404065
(cons (car e)
4041-
(map-cl-convert (cdr e) fname lam namemap defined toplevel interp opaq))))))))
4066+
(map-cl-convert (cdr e) fname lam namemap defined toplevel interp opaq globals))))))))
40424067

40434068
(define (closure-convert e) (cl-convert e #f #f #f #f #f #f #f))
40444069

src/julia.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -503,12 +503,12 @@ typedef struct {
503503
jl_sym_t *name;
504504
_Atomic(jl_value_t*) value;
505505
_Atomic(jl_value_t*) globalref; // cached GlobalRef for this binding
506-
jl_value_t *ty // add type for globals
507506
struct _jl_module_t* owner; // for individual imported bindings -- TODO: make _Atomic
508507
uint8_t constp:1;
509508
uint8_t exportp:1;
510509
uint8_t imported:1;
511510
uint8_t deprecated:2; // 0=not deprecated, 1=renamed, 2=moved to another package
511+
jl_value_t *ty; // add type for globals
512512
} jl_binding_t;
513513

514514
typedef struct {

src/module.c

+19-5
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,10 @@ JL_DLLEXPORT void jl_set_global(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *va
663663
JL_DLLEXPORT void jl_set_const(jl_module_t *m JL_ROOTING_ARGUMENT, jl_sym_t *var, jl_value_t *val JL_ROOTED_ARGUMENT)
664664
{
665665
jl_binding_t *bp = jl_get_binding_wr(m, var, 1);
666+
if (bp->ty) {
667+
jl_errorf("cannot declare %s constant; it was already declared as a typed global",
668+
jl_symbol_name(bp->name));
669+
}
666670
if (bp->value == NULL) {
667671
uint8_t constp = 0;
668672
// if (jl_atomic_cmpswap(&bp->constp, &constp, 1)) {
@@ -684,6 +688,13 @@ JL_DLLEXPORT int jl_is_const(jl_module_t *m, jl_sym_t *var)
684688
return b && b->constp;
685689
}
686690

691+
JL_DLLEXPORT jl_value_t *jl_get_binding_type(jl_module_t *m, jl_sym_t *var) {
692+
jl_binding_t *b = jl_get_binding(m, var);
693+
if (b == NULL || b->ty == NULL)
694+
return (jl_value_t*)jl_any_type;
695+
return b->ty;
696+
}
697+
687698
// set the deprecated flag for a binding:
688699
// 0=not deprecated, 1=renamed, 2=moved to another package
689700
JL_DLLEXPORT void jl_deprecate_binding(jl_module_t *m, jl_sym_t *var, int flag)
@@ -791,6 +802,10 @@ void jl_binding_deprecation_warning(jl_module_t *m, jl_binding_t *b)
791802

792803
JL_DLLEXPORT void jl_checked_assignment(jl_binding_t *b, jl_value_t *rhs) JL_NOTSAFEPOINT
793804
{
805+
if (b->ty && !jl_isa(rhs, b->ty)) {
806+
jl_errorf("Cannot assign a value of an incompatible type to the typed global %s.",
807+
jl_symbol_name(b->name));
808+
}
794809
if (b->constp) {
795810
jl_value_t *old = NULL;
796811
if (jl_atomic_cmpswap(&b->value, &old, rhs)) {
@@ -807,11 +822,6 @@ JL_DLLEXPORT void jl_checked_assignment(jl_binding_t *b, jl_value_t *rhs) JL_NOT
807822
}
808823
jl_safe_printf("WARNING: redefinition of constant %s. This may fail, cause incorrect answers, or produce other errors.\n",
809824
jl_symbol_name(b->name));
810-
// Get and set type of global binding if not already there
811-
if (b->ty == NULL) {
812-
jl_value_t *type = jl_typeof(b->value);
813-
jl_set_typeof(b->value, type);
814-
}
815825
}
816826
jl_atomic_store_relaxed(&b->value, rhs);
817827
jl_gc_wb_binding(b, rhs);
@@ -823,6 +833,10 @@ JL_DLLEXPORT void jl_declare_constant(jl_binding_t *b)
823833
jl_errorf("cannot declare %s constant; it already has a value",
824834
jl_symbol_name(b->name));
825835
}
836+
if (b->ty) {
837+
jl_errorf("cannot declare %s constant; it was already declared as a typed global",
838+
jl_symbol_name(b->name));
839+
}
826840
b->constp = 1;
827841
}
828842

src/staticdata.c

+2-1
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,8 @@ static const jl_fptr_args_t id_to_fptrs[] = {
249249
&jl_f_arrayref, &jl_f_const_arrayref, &jl_f_arrayset, &jl_f_arraysize, &jl_f_apply_type,
250250
&jl_f_applicable, &jl_f_invoke, &jl_f_sizeof, &jl_f__expr, &jl_f__typevar,
251251
&jl_f_ifelse, &jl_f__structtype, &jl_f__abstracttype, &jl_f__primitivetype,
252-
&jl_f__typebody, &jl_f__setsuper, &jl_f__equiv_typedef, &jl_f_opaque_closure_call,
252+
&jl_f__typebody, &jl_f__setsuper, &jl_f__equiv_typedef, &jl_f__set_typeof,
253+
&jl_f_opaque_closure_call,
253254
NULL };
254255

255256
typedef struct {

0 commit comments

Comments
 (0)