Automatically reimplement methods from one package to support equivalent type

Recently i’m have encountered an issue where i needed two packages. Package A exports a bunch of useful methods from some type T (also exported from A), and another package B that exported additional methods and a equivalent type S (meaning we can convert from T to S). The problem is that the methods from package A only accepts arguments with type T and not S. The most obvious solution is to explicit convert each object of type S to T before passing it to the methods. This can be very annoying some times, and another solution is to reimplement these methods for type S. Now the problem is if the number of methods exported by package A is very large, which can make the second solution unfeasible. To address this issue, i created a macro that automatically reimplement these methods for a specific type. Basicly, swapping the types in the arguments of each method and converting it in the function body before passing it to the method. This works well for now, but i’m pretty sure that there’s something wrong with it, and maybe in some situations it will break. Is there more efficients ways to accomplish the same thing, or nor even do this in the first place?

Here is the code for the macro (i have no idea how to name this thing):

macro remethods(_module, type1, type2)
		for name in names($_module)
			if isdefined($_module, name)
				obj = getfield($_module, name)
				if obj isa Function
					new_methods = []
					fmethods = filter(m -> hasfield(typeof(m.sig), :types), methods(obj))

					for method in fmethods
						ftype, arg_types... = method.sig.types
						name =
						arg_types = collect(arg_types)
						indices = findall(==($type1), arg_types)
						if !isempty(indices)
							push!(new_methods, (; name, arg_types, indices ))
					for method in new_methods
						(; name, arg_types, indices) = method
						foreach(i -> setindex!(arg_types, $type2, i) , indices)
						arg_names = [gensym(:arg) for a in arg_types]
						_args = [:($name::$T) for (name, T) in zip(arg_names, arg_types)]
						_body = map(indices) do i
							argname = arg_names[i]
							:( $argname = convert($$type1, $argname) )
							function $($_module).$(name)($(_args...); kwargs...)
								$($_module).$(name)($(arg_names...); kwargs...)
	end |> esc

Simple example:

module A
           export Person
           export introduce

           struct Person

           introduce(p::Person) = println("Hi, my name is $(p.first_name) $(p.last_name)")
       end # module A

julia> module B
           export Android

           struct Android
       end # module B~

julia> using .A, .B

julia> Jhon = Person("John", "Connor")
Person("John", "Connor")

julia> Connor = Android("Connor", "RK800")
Android("Connor", "RK800")

julia> introduce(Jhon)
Hi, my name is John Connor

julia> introduce(Connor)
ERROR: MethodError: no method matching introduce(::Android)
Closest candidates are:
  introduce(::Person) at REPL[2]:10
 [1] top-level scope
   @ REPL[8]:1

julia> Base.convert(::Type{Person}, a::Android) = Person(, "")

julia> @remethods A A.Person B.Android

julia> introduce(Connor)
Hi, my name is Connor

The above is just a simple example, my original problem was to use Luxor with types from GeometryBasics.

Edit: just fixed some obvious mistakes in the macro

1 Like