Skip to content

Commit

Permalink
add type detection for nested expressions in variable section
Browse files Browse the repository at this point in the history
  • Loading branch information
hhaensel committed Nov 30, 2024
1 parent ca365d2 commit c7ebf41
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 11 deletions.
7 changes: 4 additions & 3 deletions src/ReactiveTools.jl
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ function parse_mixin_params(params)
mixin, prefix, postfix
end

function parse_macros(expr::Expr, storage::LittleDict, m::Module)
function parse_macros(expr::Expr, storage::LittleDict, m::Module, let_block::Expr = Expr(:block, :(_ = 0)))
expr.head == :macrocall || return expr
flag = :nothing
fn = Symbol(String(expr.args[1])[2:end])
Expand All @@ -451,7 +451,7 @@ function parse_macros(expr::Expr, storage::LittleDict, m::Module)
end

reactive = flag != :non_reactive
var, ex = parse_expression(expr[1], mode, source, m)
var, ex = parse_expression(expr[1], mode, source, m, let_block)
storage[var] = ex
elseif fn == :mixin
mixin, prefix, postfix = parse_mixin_params(params)
Expand Down Expand Up @@ -618,7 +618,8 @@ macro handlers(typename, expr, handlers_fn_name = :handlers)
varnames = get_varnames(initcode, __module__)

filter!(x -> !isa(x, LineNumberNode), initcode)
parse_macros.(initcode, Ref(storage), Ref(__module__))
let_block = Expr(:block, :(_ = 0))
parse_macros.(initcode, Ref(storage), Ref(__module__), Ref(let_block))
# if no initcode is provided and typename is already defined, don't overwrite the existing type and just declare the handlers function
initcode_final = isempty(initcode) && isdefined(__module__, typename) ? Expr(:block) : :(Stipple.@type($typename, $storage))

Expand Down
49 changes: 41 additions & 8 deletions src/stipple/reactivity.jl
Original file line number Diff line number Diff line change
Expand Up @@ -247,19 +247,47 @@ function get_varname(expr)
var isa Symbol ? var : var.args[1]
end

function parse_expression!(expr::Expr, @nospecialize(mode) = nothing, source = nothing, m::Union{Module, Nothing} = nothing)
function assignment_to_conversion(expr)
expr = copy(expr)
expr.head = :call
pushfirst!(expr.args, :convert)
expr.args[2] = expr.args[2].args[2]
expr
end

function let_eval!(expr, let_block, m::Module)
with_type = expr.args[1] isa Expr && expr.args[1].head == :(::)
var = with_type ? expr.args[1].args[1] : expr.args[1]
let_expr = Expr(:let, let_block, Expr(:block, with_type ? assignment_to_conversion(expr) : expr.args[2]))
val = try
@eval m $let_expr
catch ex
with_type || @info "Could not infer type of $var, setting it to `Any`, consider adding a type annotation"
:__Any__
end
with_type || (T = typeof(val))

val === :__Any__ || push!(let_block.args, :($var = R{$T}($val)))
T = val === :__Any__ ? Any : T
return val, T
end

function parse_expression!(expr::Expr, @nospecialize(mode) = nothing, source = nothing, m::Union{Module, Nothing} = nothing, let_block::Union{Expr, Nothing} = nothing)
expr = find_assignment(expr)

Rtype = isnothing(m) || ! isdefined(m, :R) ? :(Stipple.R) : :R

(isa(expr, Expr) && contains(string(expr.head), "=")) ||
error("Invalid binding expression -- use it with variables assignment ex `@in a = 2`")

error("Invalid binding expression -- use it with variables assignment ex `@in a = 2`")
source = (source !== nothing ? String(strip(string(source), collect("#= "))) : "")

# args[end] instead of args[2] because of potential LineNumberNode
var = expr.args[1]
mode === nothing && (mode = PRIVATE)
context = isnothing(m) ? @__MODULE__() : m

let_block === nothing || ((val, T) = let_eval!(expr, let_block, m))

mode = mode isa Symbol && ! isdefined(context, mode) ? :(Stipple.$mode) : mode
type = if isa(var, Expr) && var.head == Symbol("::")
Expand All @@ -268,22 +296,27 @@ function parse_expression!(expr::Expr, @nospecialize(mode) = nothing, source = n
else # no type is defined, so determine it from the type of the default value
try
# add type definition `::R{T}` to the var where T is the type of the default value
T = @eval(context, $(expr.args[end])) |> typeof
T = let_block === nothing ? typeof(@eval(context, $(expr.args[end]))) : T
expr.args[1] = :($var::$Rtype{$T})
Rtype
catch ex
# if the default value is not defined, we can't infer the type
# so we just set the type to R{Any}
@info "Could not infer type of $var, setting it to R{Any}"
expr.args[1] = :($var::$Rtype{Any})
:($Rtype{Any})
end
end

expr.args[end] = :($type($(expr.args[end]), $mode, false, false, $source))

expr.args[1].args[1], expr
varname = expr.args[1].args[1]
# evaluate the expression in the context of the module and append the corresponding assignment to the let_block
#val = let_eval!(expr, let_block, context)
varname, expr
end

parse_expression(expr::Expr, mode = nothing, source = nothing, m = nothing) = parse_expression!(copy(expr), mode, source, m)
parse_expression(expr::Expr, mode = nothing, source = nothing, m = nothing, let_block::Expr = Expr(:block, :(_ = 0))) = parse_expression!(copy(expr), mode, source, m, let_block)

macro var_storage(expr)
m = __module__
Expand Down

0 comments on commit c7ebf41

Please sign in to comment.