diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 1eeea46..5cf8c5d 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -19,6 +19,7 @@ jobs: - '1.6' - '1.9' - '1.10' + - '1.12' - 'nightly' os: - ubuntu-latest diff --git a/.gitignore b/.gitignore index 27d5738..97105e3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ -/Manifest.toml -/docs/Manifest.toml +/Manifest*.toml +/docs/Manifest*.toml /docs/build/ *.code-workspace -Manifest.toml -LocalPreferences.toml \ No newline at end of file +Manifest*.toml +LocalPreferences.toml +.vscode/ \ No newline at end of file diff --git a/src/define_interface.jl b/src/define_interface.jl index 72fbf24..c10625e 100644 --- a/src/define_interface.jl +++ b/src/define_interface.jl @@ -2,34 +2,34 @@ _propertynames(T::Type) = Base.fieldnames(T) _propertynames(x) = Base.propertynames(x) @generated function validate_properties(::Type{S}, delegated_fieldnames::Type{T}) where {S, T <: Tuple} - output = Expr(:block, :(unique_properties = Set{Symbol}($Base.fieldnames($S)))) + output_args = Any[:(local unique_properties = Set{Symbol}($Base.fieldnames($S)))] recursive = fieldtype(T, 1) === Val{:recursive} for i in 2:fieldcount(T) key = fieldtype(T, i) props = Symbol(key, :_properties) child_T = fieldtype(S, key) if recursive - push!(output.args, :($props = Set{Symbol}( $_propertynames($child_T)))) + push!(output_args, :(local $props = Set{Symbol}( $_propertynames($child_T)))) else - push!(output.args, :($props = Set{Symbol}( $fieldnames($child_T)))) + push!(output_args, :(local $props = Set{Symbol}( $fieldnames($child_T)))) end - push!( output.args, :( diff = $intersect(unique_properties, $(props)) ), :(!isempty(diff) && error("Duplicate properties `$(sort(collect(diff)))` found for type $($(S)) in child `$($(QuoteNode(key)))::$($(child_T))` ")), :($union!(unique_properties, $props))) + push!( output_args, :( diff = $intersect(unique_properties, $(props)) ), :(!isempty(diff) && error("Duplicate properties `$(sort(collect(diff)))` found for type $($(S)) in child `$($(QuoteNode(key)))::$($(child_T))` ")), :($union!(unique_properties, $props))) end - push!(output.args, :(return nothing)) - return output + push!(output_args, :(return nothing)) + return Expr(:block, output_args...) end @generated function _propertynames(::Type{S}, delegated_fields::Type{T}) where {S, T <: Tuple} Base.isstructtype(S) || error("$S is not a struct type") - output = Expr(:tuple, QuoteNode.(fieldnames(S))...) + output_args = Any[QuoteNode.(fieldnames(S))...] recursive = fieldtype(T, 1) === Val{:recursive} for i in 2:fieldcount(T) key = fieldtype(T,i) Si = fieldtype(S, key) - push!(output.args, recursive ? :($_propertynames($(Si))...) : :($fieldnames($(Si))...)) + push!(output_args, recursive ? :($_propertynames($(Si))...) : :($fieldnames($(Si))...)) end - return output + return Expr(:tuple, output_args...) end _propertynames(x, delegated_fields) = _propertynames(typeof(x), delegated_fields) @@ -88,6 +88,7 @@ If `ensure_unique == true`, throws an error when there are nonunique names in th """ function properties_interface(T; delegated_fields, recursive::Bool=false, ensure_unique::Bool=true, kwargs...) + @nospecialize if haskey(kwargs, :is_mutable) Base.depwarn("Passing `is_mutable` kwarg when `interface=properties` is now deprecated", Symbol("@define_interface")) is_mutable = get_kwarg(Bool, kwargs, :is_mutable, false) @@ -110,14 +111,15 @@ function properties_interface(T; delegated_fields, recursive::Bool=false, ensure _setproperty = :($ForwardMethods._setproperty!($obj::$T, $name::Symbol, $value) = $ForwardMethods._setproperty!($obj, $delegated_fields_tuple_type, $name, $value)) setproperty = :($Base.setproperty!($obj::$T, $name::Symbol, $value) = $ForwardMethods._setproperty!($obj, $name, $value)) - output = Expr(:block, line_num) + output_args = Any[] if ensure_unique - push!(output.args, :($validate_properties($T, $delegated_fields_tuple_type))) + push!(output_args, :($validate_properties($T, $delegated_fields_tuple_type))) end - push!(output.args, map(linenum!, (_propertynames, _propertynamesT, propertynames, _getproperty, getproperty))...) + push!(output_args, map(linenum!, (_propertynames, _propertynamesT, propertynames, _getproperty, getproperty))...) if is_mutable - push!(output.args, map(linenum!, (_setproperty, setproperty))...) + push!(output_args, map(linenum!, (_setproperty, setproperty))...) end + output = Expr(:block, line_num, output_args...) return wrap_define_interface(T, :properties, output) end @@ -138,6 +140,7 @@ Any values provided in `omit` are excluded from the generator expression above. """ function equality_interface(T; omit::AbstractVector{Symbol}=Symbol[], equality_op::Symbol=:(==), compare_fields::Symbol=:fieldnames) + @nospecialize equality_op in (:(==), :isequal) || error("equality_op (= $equality_op) must be one of (==, isequal)") if compare_fields == :fieldnames getvalue = :($Base.getfield) @@ -166,9 +169,10 @@ end @method_def_constant define_interface_method(::Val{::Symbol}) define_interfaces_available function define_interface_expr(T, kwargs::Dict{Symbol,Any}=Dict{Symbol,Any}(); _sourceinfo) + @nospecialize interfaces = interface_kwarg!(kwargs) omit = omit_kwarg!(kwargs) - output = Expr(:block) + output_args = Any[] interfaces_available = define_interfaces_available() for interface in interfaces interface in interfaces_available || error("No interface found with name $interface -- must be one of `$interfaces_available`") @@ -176,12 +180,12 @@ function define_interface_expr(T, kwargs::Dict{Symbol,Any}=Dict{Symbol,Any}(); _ current_line_num[] = _sourceinfo try - push!(output.args, f(T; omit, kwargs...)) + push!(output_args, f(T; omit, kwargs...)) finally current_line_num[] = nothing end end - return output + return Expr(:block, output_args...) end """ diff --git a/src/forward_interface.jl b/src/forward_interface.jl index 7870dd2..0ab706e 100644 --- a/src/forward_interface.jl +++ b/src/forward_interface.jl @@ -5,6 +5,7 @@ interface_at_macroexpand_time(x) = true base_forward_expr(f, args...) = Expr(:call, f, args...) function forward_interface_args(T) + @nospecialize t = gensym(arg_placeholder) obj_arg = object_argument(t, T) type_arg = type_argument(T) @@ -102,6 +103,7 @@ according to the value of `index_style_linear` Any function names specified in `omit::AbstractVector{Symbol}` will not be defined """ function array_interface(T; index_style_linear::Bool, omit::AbstractVector{Symbol}=Symbol[]) + @nospecialize obj_arg, type_arg, call_expr = forward_interface_args(T) method_signatures = Any[ @@ -242,13 +244,14 @@ function getfields_interface(T; field::Union{Nothing,Symbol}=nothing, omit::Abst return wrap_define_interface(T, :getfields, Base.remove_linenums!(quote local omit_fields = $(Expr(:tuple, QuoteNode.(omit)...)) local fields = fieldnames($T) - local def_fields_expr = Expr(:block) local var = gensym("x") + local def_fields_expr_args = Any[] for field in fields if field ∉ omit_fields - push!(def_fields_expr.args, :($field($var::$$T) = Base.getfield($var, $(QuoteNode(field))))) + push!(def_fields_expr_args, :($field($var::$$T) = Base.getfield($var, $(QuoteNode(field))))) end end + local def_fields_expr = Expr(:block, def_fields_expr_args...) eval(def_fields_expr) nothing end)) @@ -265,13 +268,14 @@ function setfields_interface(T; field::Union{Nothing,Symbol}=nothing, omit::Abst return wrap_define_interface(T, :setfields, Base.remove_linenums!(quote local omit_fields = $(Expr(:tuple, QuoteNode.(omit)...)) local fields = fieldnames($T) - local def_fields_expr = Expr(:block) local var = gensym("x") + local def_fields_expr_args = Any[] for field in fields if field ∉ omit_fields - push!(def_fields_expr.args, :($(Symbol(string(field)*"!"))($var::$$T, value) = Base.setfield!($var, $(QuoteNode(field)), value))) + push!(def_fields_expr_args, :($(Symbol(string(field)*"!"))($var::$$T, value) = Base.setfield!($var, $(QuoteNode(field)), value))) end end + local def_fields_expr = Expr(:block, def_fields_expr_args...) eval(def_fields_expr) nothing end)) @@ -309,8 +313,7 @@ function forward_interface_expr(T, kwargs::Dict{Symbol,Any}=Dict{Symbol,Any}(); field_funcs = nothing end - _output = Expr(:block) - + _output_args = Any[] available_interfaces = forward_interfaces_available() for interface in interfaces @@ -325,16 +328,16 @@ function forward_interface_expr(T, kwargs::Dict{Symbol,Any}=Dict{Symbol,Any}(); isnothing(field_funcs.type_func) && error("Only fieldname mode for `field` (= $field) supported for interface (= $interface_value)") end signatures = f(T; omit, kwargs...) - output = Expr(:block) + output_args = Any[] for signature in signatures - push!(output.args, forward_method_signature(T, field_funcs, map_func, signature; _sourceinfo)) + push!(output_args, forward_method_signature(T, field_funcs, map_func, signature; _sourceinfo)) end - push!(_output.args, output) + push!(_output_args, Expr(:block, output_args...)) else - push!(_output.args, f(T; omit, kwargs...)) + push!(_output_args, f(T; omit, kwargs...)) end end - return _output + return Expr(:block, _output_args...) end """ diff --git a/src/forward_methods.jl b/src/forward_methods.jl index 5c6a1e8..91d0ece 100644 --- a/src/forward_methods.jl +++ b/src/forward_methods.jl @@ -106,24 +106,27 @@ function forward_method_signature(Type, field_funcs::FieldFuncExprs, map_func::F if !found error("No argument matching type $Type in input = `$input`") end - func_body = Expr(:call, funcname) + func_body_args = Any[funcname] if !isempty(kwargs) - push!(func_body.args, Expr(:parameters, kwargs...)) + push!(func_body_args, Expr(:parameters, kwargs...)) end - push!(func_body.args, output_args...) - body_block = Expr(:block) + push!(func_body_args, output_args...) + func_body = Expr(:call, func_body_args...) + body_block_args = Any[] if !isnothing(_sourceinfo) - push!(body_block.args, _sourceinfo) + push!(body_block_args, _sourceinfo) end - push!(body_block.args, found_arg_expr) + push!(body_block_args, found_arg_expr) mapped_body = !found_arg_is_type ? map_func(found_input_arg, func_body) : func_body - push!(body_block.args, mapped_body) + push!(body_block_args, mapped_body) + body_block = Expr(:block, body_block_args...) - new_sig = Expr(:call, funcname) + new_sig_args = Any[funcname] if !isempty(kwargs) - push!(new_sig.args, Expr(:parameters, kwargs...)) + push!(new_sig_args, Expr(:parameters, kwargs...)) end - push!(new_sig.args, input_args...) + push!(new_sig_args, input_args...) + new_sig = Expr(:call, new_sig_args...) if !isnothing(whereparams) new_sig = Expr(:where, new_sig, whereparams...) end @@ -226,7 +229,7 @@ function forward_methods_expr(Type, field_expr, args...; _sourceinfo=nothing) method_exprs = args end - output = Expr(:block) + output_args = Any[] for arg in method_exprs _args = @switch arg begin @case Expr(:block, args...) @@ -235,10 +238,10 @@ function forward_methods_expr(Type, field_expr, args...; _sourceinfo=nothing) [arg] end for arg in _args - push!(output.args, forward_method_signature(Type, field_funcs, map_func, arg; _sourceinfo)) + push!(output_args, forward_method_signature(Type, field_funcs, map_func, arg; _sourceinfo)) end end - return output + return Expr(:block, output_args...) end """ diff --git a/src/util.jl b/src/util.jl index 2d481b7..e2def64 100644 --- a/src/util.jl +++ b/src/util.jl @@ -50,13 +50,13 @@ replace_placeholder(x, replace_values) = (x, false) function replace_placeholder(x::Expr, replace_values::Vector{<:Pair{Symbol,<:Any}}) replaced = false - new_expr = Expr(x.head) + new_expr_args = Any[] for arg in x.args new_arg, arg_replaced = replace_placeholder(arg, replace_values) - push!(new_expr.args, new_arg) + push!(new_expr_args, new_arg) replaced |= arg_replaced end - return new_expr, replaced + return Expr(x.head, new_expr_args...), replaced end identity_map_expr(obj_expr, forwarded_expr) = forwarded_expr @@ -113,7 +113,7 @@ function omit_kwarg!(kwargs::Dict{Symbol,Any}) end end -function get_kwarg(::Type{T}, kwargs, key::Symbol, default) where {T} +function get_kwarg(T::Type, kwargs, key::Symbol, default) value = get(kwargs, key, default) value isa T || error("$key (= $value) must be a $T, got typeof($key) = $(typeof(value))") return value diff --git a/test/Project.toml b/test/Project.toml index a5cbb92..1bc7897 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -1,6 +1,5 @@ [deps] Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" -JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" TestItemRunner = "f8b46487-2199-4994-9208-9a1283c18c0a" TestItems = "1c621080-faea-4a02-84b6-bbd5e436b8fe" diff --git a/test/runtests.jl b/test/runtests.jl index 596e423..5f86725 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,7 +1,7 @@ -using ForwardMethods using TestItemRunner if VERSION ≥ v"1.9" + using ForwardMethods using Aqua Aqua.test_all(ForwardMethods) end diff --git a/test/setup.jl b/test/setup.jl index 3f39f79..6a5b24d 100644 --- a/test/setup.jl +++ b/test/setup.jl @@ -3,7 +3,7 @@ using TestItemRunner, TestItems @testsnippet SetupTest begin using ForwardMethods.MLStyle - using JET, Test, TestingUtilities + using Test, TestingUtilities macro test_throws_compat(ExceptionType, message, expr) output = Expr(:block, __source__, :($Test.@test_throws $ExceptionType $expr)) diff --git a/test/test_forward_methods.jl b/test/test_forward_methods.jl index 15b5110..56dff8a 100644 --- a/test/test_forward_methods.jl +++ b/test/test_forward_methods.jl @@ -192,9 +192,7 @@ @Test B([0])[1] == 0 c = C([1]) @Test length(c) == 1 - @static if VERSION >= v"1.9" - @test_opt length(c) - end + end end end \ No newline at end of file