-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix Aqua's reported piracies and method ambiguities #85
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #85 +/- ##
==========================================
- Coverage 75.50% 75.46% -0.04%
==========================================
Files 19 19
Lines 800 803 +3
==========================================
+ Hits 604 606 +2
- Misses 196 197 +1 ☔ View full report in Codecov by Sentry. 🚨 Try these New Features:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for taking care of this! I have some minor questions about the types marked as own
but on a quick skim the rest looks good.
test/test_aqua.jl
Outdated
@@ -1,7 +1,9 @@ | |||
@testitem "Aqua" tags=[:aqua] begin | |||
using Aqua | |||
import QuantumInterface as QI | |||
own_types = [QI.AbstractBra, QI.AbstractKet, QI.AbstractSuperOperator, QI.AbstractOperator] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you elaborate why these need to be marked as own
? I am not sure whether this would be consistent with how independent packages downstream of QuantumInterface (e.g. QuantumOptics or QuantumCummulants) -- I want to make sure there are some precautions put in place for potential future issues.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I understand correctly, a type pyracy is when we expand a function definition with methods that take a certain type, but we neither defined the original function nor the type.
That was occurring all the time in files such as basic_ops_homogeneous.jl, basic_ops_inhomogeneous.jl, basic_superops.jl
with methods that extend Base.(*)
or Base.(+)
The last paragraph in the piracy section here gave me the impression that this was indeed happening intentionally. QuantumInterface
defines the interface that is then used in other packages. So we tell Aqua that QuantumInterface and QuantumSymbolics are really cousin packages (since we avoided extending Base in QuantumInterface by design)
Aqua docs stating the following too gave me assurance that this is right.
this is useful for testing packages that deliberately commit some type piracies
Note: the functions really take Symbolic{T}
not T
directly, this seems to be worked around somewhere (same with Vector{T}
) such that piracy detection is not tripped by these simple wrapper types.
Hi! Just a friendly bump on this. I see most of the piracies were masked by marking a bunch of types as owned. This will be a bit fragile. Do you have any insight into which piracies this addresses -- upstreaming them to QuantumInterface would be a safer long-term solution. |
Hi @Krastanov, I replied to your code review itself a while ago. Edit: wow it seems that I'm the one who has been duped by GitHub's UI, my apologies. |
No worries, I have made the same mistake in the past. |
@@ -40,9 +40,9 @@ function Base.:(*)(c, x::Symbolic{T}) where {T<:QObj} | |||
SScaled{T}(c, x) | |||
end | |||
end | |||
Base.:(*)(x::Symbolic{T}, c) where {T<:QObj} = c*x | |||
Base.:(*)(x::Symbolic{T}, c::Number) where {T<:QObj} = c*x |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Motivation here was to avoid ambiguities with other packages,
But I'm not sure if it should be ::Number
or ::{Union{Number, Symbolic{Number}}}
to allow p*tr(q)
just as tr(q)*p
is allowed. The test suits for QuantumSymbolics and QuantumSavory pass right now, but I wasn't sure if that is a valid operation since it was allowed before my changes.
test/test_aqua.jl
Outdated
@@ -1,7 +1,9 @@ | |||
@testitem "Aqua" tags=[:aqua] begin | |||
using Aqua | |||
import QuantumInterface as QI | |||
own_types = [QI.AbstractBra, QI.AbstractKet, QI.AbstractSuperOperator, QI.AbstractOperator] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I understand correctly, a type pyracy is when we expand a function definition with methods that take a certain type, but we neither defined the original function nor the type.
That was occurring all the time in files such as basic_ops_homogeneous.jl, basic_ops_inhomogeneous.jl, basic_superops.jl
with methods that extend Base.(*)
or Base.(+)
The last paragraph in the piracy section here gave me the impression that this was indeed happening intentionally. QuantumInterface
defines the interface that is then used in other packages. So we tell Aqua that QuantumInterface and QuantumSymbolics are really cousin packages (since we avoided extending Base in QuantumInterface by design)
Aqua docs stating the following too gave me assurance that this is right.
this is useful for testing packages that deliberately commit some type piracies
Note: the functions really take Symbolic{T}
not T
directly, this seems to be worked around somewhere (same with Vector{T}
) such that piracy detection is not tripped by these simple wrapper types.
I see, that makes sense, thank you! So an example of an offender would be
I am wondering, can |
I'm won't be exactly sure why, but that doesn't seem to work. At the simplest case
|
The way I'm thinking about it is that QuantumInterface is that one that owns What we are saying here is that QuantumSymbolics gets a special treatment because it is really an extension of QuantumInterface by the same developers. QuantumInterface itself says |
You are right, it is just that there are multiple groups coordinating on this work, and having as much of the coordination automated would help a lot. In particular, if we just I posted about this on slack. Let's wait a bit to see if anyone has suggestions there. |
Actually, how about we keep your implementation as is, but also add the following extra test?
The subset check might be too harsh. An intersection check might be enough. |
Ok, that is the use case I didn't think of on my own
Yeah, doesn't seem like a bad idea. I went through few iterations to get to something that doesn't show false-positives. # get it to work without errors
all([Base.unwrap_unionall(m.sig).types[2:end] ⊆ own_types for m in Aqua.Piracy.hunt(QuantumSymbolics)])
get_method_args(m::Method) = Base.unwrap_unionall(m.sig).types[2:end] # for readability here
# Transform to filter to see the cases that are not passing
filter(m -> !(get_method_args(m) ⊆ own_types), Aqua.Piracy.hunt(QuantumSymbolics)) # 18 false-positives
# Use intersection test. Subset is too strict because it keeps out methods which take additional arguments
#not related to QuantumSymbolics. Example ptrace(x::Symbolic{QuantumInterface.AbstractOperator}, s)
filter(m -> isdisjoint(get_method_args(m), own_types), Aqua.Piracy.hunt(QuantumSymbolics)) #10 false-positives
# Some arguments need a little normalization. Such as maketerm(::Type{<:Symbolic{<:QObj}},...)
function normalize(arg)
if (arg isa UnionAll) && (arg.body <: Type) arg = arg.body.parameters[1] end
if (arg isa Core.TypeofVararg) arg = arg.T end
if (arg isa TypeVar) arg = arg.ub end
return arg
end
filter(m -> isdisjoint(normalize.(get_method_args(m)), own_types), Aqua.Piracy.hunt(QuantumSymbolics)) #8 false-positives
# What we really need is to check if an arg is a subtype of Symbolic{<:QObj}, to be able to match most edge cases
filter(m -> !any(normalize.(get_method_args(m)) .<: Symbolic{<:QObj}), Aqua.Piracy.hunt(QuantumSymbolics)) # 0 false-positives Here is a proof of it catching an introduced piracy module modA
import QuantumInterface: AbstractBra, AbstractKet
import Base.println
import SymbolicUtils.Symbolic
Base.println(x::T) where T<:Union{AbstractBra, AbstractKet} = 1
Base.println(x::Symbolic{T}) where T<:Union{AbstractBra, AbstractKet} = 2
end
filter(m -> !any(normalize.(get_method_args(m)) .<: Union{Symbolic{<:QObj}}), Aqua.Piracy.hunt(modA))
#[1] println(x::T) where T<:Union{AbstractBra, AbstractKet} @ Main.modA REPL[53]:5 |
This is wonderfully done, thank you for working it out! |
I'm good with this. Might have gotten carried away. |
Thank you, this is very much appreciated. Could you message me at [email protected] with your name, github username, and bounty number so I can forward you the bounty payment details? |
Fix all of Aqua's reported piracies and method ambiguities. (#65)
Precisely 37 out of the 39 potential piracies are avoided by informing Aqua that types from QuantumInterface are in fact our own. The last two are fixed by adjusting the
* and +
functions that accept aVararg
, it had a problem for the case whenN=0
. This solution avoids both the unbound_type_param and the piracy, and seems to be a reasonable design to not allow zero arguments passed to the function (similar to Base operators themselves)PR checklist