Revise type annotations without edits?

Because of the upcoming world age-partitioned semantics (PR #57253), Revise can pull off type redefinitions by reassigning the const name (Issue #18).

Let’s say we start with:

julia> begin
       struct X{T} x::T end;
       unwrap(a::X) = a.x; # argument annotation, body uses field
       square(a::X) = unwrap(a) ^ 2; # argument annotation
       struct Y{T} y::X{T} end; # field annotation, uses a parameter
       wrap(x) = X(x); # const X name in body
       wrap(1)
       end
X{Int64}(1)

Now we’re allowed to re-evaluate the struct definition (in Revise we’d just edit the source file):

julia> struct X end; # removes parameter and field

The obsolete type object still exists, which makes sense considering that there may still be instances around. Let’s look at the consequences for everything else:

julia> methods(unwrap) # not reevaluated, good because body needs change
# 1 method for generic function "unwrap" from Main:
 [1] unwrap(a::@world(X, 38433:38439))
     @ REPL[2]:1

julia> methods(square) # not reevaluated, could be because it fits new type
# 1 method for generic function "square" from Main:
 [1] square(a::@world(X, 38433:38439))
     @ REPL[3]:1

julia> dump(Y) # not reevaluated, good to avoid TypeError
UnionAll
  var: TypeVar
    name: Symbol T
    lb: Union{}
    ub: abstract type Any
  body: struct Y{T} <: Any
    y::@world(X, 38433:38439){T}

julia> wrap(1) # invalidated but fails, good because needs change
ERROR: MethodError: no method matching X(::Int64)
...

So besides invalidating methods like wrap that use the name X, the type redefinition doesn’t change annotations because the definitions used the type object at the time, not the name itself. In the case of unwrap and Y, that’s fine because we need to edit and reevaluate to make things work anyway. However, square happens to not need edits for its reevaluation, which makes things difficult for Revise’s file-editing design. It’d be strange to add useless lines just to trigger a change, what will be the course of action there?

Separately tracking variables used at definition-time and annotation structure to automatically reevaluate suitable definitions is not a solution because we’re allowed to reassign const X = 1 or anything that isn’t a type, which will throw errors upon any annotations. That’s a lot of overhead for nothing. However, it does seem nice when we’re just changing types and have many definitions like square that happen to fit the newer type.

2 Likes