Can i propagate @invoke?

Hey,

I always thought that @invoke would not allow functions in the call stack to know the true argument’s type, but i just realised today that i am wrong:

abstract type AbstractA end
struct A <: AbstractA 
    x::Float64
end
a = A(1.2)

f(::AbstractA) = "AbstractA "
f(::A) = "A"

f(a)
@invoke f(a::AbstractA) # returns "AbstractA"  as wanted. 

g(x) = f(x)
g(a)
@invoke g(a::AbstractA) # gives "A"... 

Is there another way to say that i want the mockup to be respected by the whole call-stack ? The issue is that I tend to write tests like :

@test g(a) == @invoke g(a::AbstractA)

to validate the method for a::A against the method written for the abstract type. And thus all my test tend to pass even if they are wrong… since both side of the equality actually call the same underlying methods !

I’m not sure propagating @invoke would be a good idea, as methods could be defined for all concrete subtypes, but not the abstract supertype. E.g. in

abstract type AbstractAB end
struct A <: AbstractAB end
struct B <: AbstractAB end

f(::A) = "a"
f(::B) = "b"
# Does not make sense for AbstractAB, so we won't define f(::AbstractAB)

g(ab::AbstractAB) = f(ab) ^ 2

a @propagate_invoke g(a::AbstractAB) would use @invoke f(a::AbstractAB) which fails.

As another example, with

function to_Int64(i::Integer)
    return Int64(i)
end

(which works fine for sufficiently small Int128 and BigInt) a @propagate_invoke to_Int64(1::Integer) would throw at @invoke Int64(1::Integer).


Instead of using a @propagate_invoke at a higher-level function g which does not distinguish between AbstractA and A, you could add @invoke tests for the lower-level functions f which are actually defined separately for AbstractA and A. When all such test succeed, g should also work fine.

Yeah but here I know that its defined since I have defined it myself :wink:

Yes this is what I am currently doing, writing @invoke tests for the underlying functions instead. But still, my tests stood wrong for more than 2 years before me figuring it out.

I think this is a bit of an xy-problem. Instead of using @invoke to test whether your function work with some abstract type, you should just define a new type that fulfils the interface for testing purposes.
E.g.

struct MockA <: AbstractA
end
# maybe additional methods that are required

This would be a much stronger/meaningful test as well.

2 Likes