Type piracy
Type piracy is a term used to describe adding methods to a foreign function with only foreign arguments. This is considered bad practice because it can cause unexpected behavior when the function is called, in particular, it can change the behavior of one of your dependencies depending on if your package is loaded or not. This makes it hard to reason about the behavior of your code, and may introduce bugs that are hard to track down.
See Julia documentation for more information about type piracy.
Examples
Say that PkgA
is foreign, and let's look at the different ways that PkgB
extends its function bar
.
module PkgA
struct C end
bar(x::C) = 42
bar(x::Vector) = 43
end
module PkgB
import PkgA: bar, C
struct D end
bar(x::C) = 1
bar(xs::D...) = 2
bar(x::Vector{<:D}) = 3
bar(x::Vector{D}) = 4 # slightly bad (may cause invalidations)
bar(x::Union{C,D}) = 5 # slightly bad (a change in PkgA may turn it into piracy)
# (for example changing bar(x::C) = 1 to bar(x::Union{C,Int}) = 1)
end
The following cases are enumerated by the return values in the example above:
- This is the worst case of type piracy. The value of
bar(C())
can be either1
or42
and will depend on whetherPkgB
is loaded or not. - This is also a bad case of type piracy.
bar()
throws aMethodError
with onlyPkgA
available, and returns2
withPkgB
loaded.PkgA
may add a method forbar()
that takes no arguments in the future, and then this is equivalent to case 1. - This is a moderately bad case of type piracy.
bar(Union{}[])
returns3
whenPkgB
is loaded, and43
whenPkgB
is not loaded, although neither of the occurring types are defined inPkgB
. This case is not as bad as cases 1 and 2, because it is only about behavior aroundUnion{}
, which has no instances. - Depending on ones understanding of type piracy, this could be considered piracy as well. In particular, this may cause invalidations.
- This is a slightly bad case of type piracy. In the current form,
bar(C())
returns42
as the dispatch onUnion{C,D}
is less specific. However, a future change inPkgA
may change this behavior, e.g. by changingbar(x::C)
tobar(x::Union{C,Int})
the callbar(C())
would become ambiguous.
The test function below currently only checks for cases 1 and 2.
Test function
Aqua.test_piracies
— Functiontest_piracies(m::Module)
Test that m
does not commit type piracies.
Keyword Arguments
broken::Bool = false
: If true, it uses@test_broken
instead of@test
and shortens the error message.skip_deprecated::Bool = true
: If true, it does not check deprecated methods.treat_as_own = Union{Function, Type}[]
: The types in this container are considered to be "owned" by the modulem
. This is useful for testing packages that deliberately commit some type piracies, e.g. modules adding higher-level functionality to a lightweight C-wrapper, or packages that are extendingStatsAPI.jl
.