Skip to content
This repository has been archived by the owner on May 29, 2024. It is now read-only.

Commit

Permalink
tweak(interaction/externals): better subtype check
Browse files Browse the repository at this point in the history
Because <: doesn't work with typevars, we need to use the more fiddly
approach more widely, so we might as well extract and document the logic
then apply it more widely.

This allows for the correct handling of methods with typevars in more
places.
  • Loading branch information
tecosaur committed May 23, 2024
1 parent 21ec641 commit d814bbc
Showing 1 changed file with 51 additions and 20 deletions.
71 changes: 51 additions & 20 deletions src/interaction/externals.jl
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,50 @@ function Base.read(dataset::DataSet)
@advise _read(dataset, as)
end

"""
issubtype(X::Type, T::Union{Type, TypeVar})
issubtype(x::X, T::Union{Type, TypeVar})
Check if `X` is indeed a subtype of `T`.
This is a tweaked version of `isa` that can (mostly) handle `TypeVar` instances.
"""
function issubtype(X::Type, T::Union{Type, TypeVar})
if T isa TypeVar
# We can't really handle complex `TypeVar` situations,
# but we'll give the very most basic a shot, and cross
# our fingers with the rest.
if T.lb isa Type && T.ub isa Type
T.lb <: X <: T.ub
else
false
end
else
@assert T isa Type
X <: T
end
end

issubtype(x, T::Union{Type, TypeVar}) =
issubtype(typeof(x), T::Union{Type, TypeVar})

"""
isparamsubtype(X, T::Union{Type, TypeVar}, Tparam::Union{Type, TypeVar}, paramT::Type)
Check that `arg` is of type `T`, where `T` may be parameterised by
`Tparam` which itself takes on the type `paramT`.
More specifically, when `Tparam == Type{T}`, this checks that
`arg` is of type `paramT`, and returns `issubtype(arg, T)` otherwise.
"""
function isparamsubtype(X::Type, T::Union{Type, TypeVar}, Tparam::Union{Type, TypeVar}, paramT::Type)
if T isa TypeVar && Type{T} == Tparam
X <: paramT
else
issubtype(X, T)
end
end

"""
_read(dataset::DataSet, as::Type)
Expand Down Expand Up @@ -189,7 +233,7 @@ function _read(dataset::DataSet, as::Type)
dataset.loaders)
end
for loader in potential_loaders
load_fn_sigs = filter(fnsig -> loader isa fnsig.types[2], all_load_fn_sigs)
load_fn_sigs = filter(fnsig -> issubtype(loader, fnsig.types[2]), all_load_fn_sigs)
# Find the highest priority load function that can be satisfied,
# by going through each of the storage backends one at a time:
# looking for the first that is (a) compatible with a load function,
Expand All @@ -199,20 +243,7 @@ function _read(dataset::DataSet, as::Type)
supported_storage_types = Vector{Type}(
filter(!isnothing, typeify.(storage.type)))
valid_storage_types =
filter(stype -> let accept = load_fn_sig.types[3]
if accept isa TypeVar
# We can't really handle complex `TypeVar` situations,
# but we'll give the very most basic a shot, and cross
# our fingers with the rest.
if load_fn_sig.types[4] == Type{load_fn_sig.types[3]}
stype <: as
else
accept.lb <: stype <: accept.ub
end
else # must be a Type
stype <: accept
end
end,
filter(stype -> isparamsubtype(stype, load_fn_sig.types[3], load_fn_sig.types[4], as),
supported_storage_types)
for storage_type in valid_storage_types
datahandle = open(dataset, storage_type; write = false)
Expand All @@ -238,7 +269,7 @@ function _read(dataset::DataSet, as::Type)
throw(UnsatisfyableTransformer(dataset, DataLoader, [qtype]))
else
loadertypes = map(
f -> QualifiedType( # Repeat the logic from `valid_storage_types`
f -> QualifiedType( # Repeat the logic from `valid_storage_types` / `isparamsubtype`
if f.types[3] isa TypeVar
if f.types[4] == Type{f.types[3]}
as
Expand All @@ -248,8 +279,8 @@ function _read(dataset::DataSet, as::Type)
else
f.types[3]
end),
filter(f -> any(l -> l isa f.types[2], potential_loaders),
all_load_fn_sigs))
filter(f -> any(l -> issubtype(l, f.types[2]), potential_loaders),
all_load_fn_sigs)) |> unique
throw(UnsatisfyableTransformer(dataset, DataStorage, loadertypes))
end
end
Expand Down Expand Up @@ -358,7 +389,7 @@ function Base.write(dataset::DataSet, info::T) where {T}
filter(writer -> any(st -> (qtype, st, mod=dataset.collection.mod), writer.type),
dataset.writers)
for writer in potential_writers
write_fn_sigs = filter(fnsig -> writer isa fnsig.types[2], all_write_fn_sigs)
write_fn_sigs = filter(fnsig -> issubtype(writer, fnsig.types[2]), all_write_fn_sigs)
# Find the highest priority save function that can be satisfied,
# by going through each of the storage backends one at a time:
# looking for the first that is (a) compatible with a save function,
Expand All @@ -368,7 +399,7 @@ function Base.write(dataset::DataSet, info::T) where {T}
supported_storage_types = Vector{Type}(
filter(!isnothing, typeify.(storage.type)))
valid_storage_types =
filter(stype -> stype <: write_fn_sig.types[3],
filter(stype -> issubtype(stype, write_fn_sig.types[3]),
supported_storage_types)
for storage_type in valid_storage_types
datahandle = open(dataset, storage_type; write = true)
Expand Down

0 comments on commit d814bbc

Please sign in to comment.