Skip to content

Commit

Permalink
Fix the 2D api new variance problem.
Browse files Browse the repository at this point in the history
Not exactly sure why this broke over the holidays,
maybe we should switch to tracking the Manifest.toml.
  • Loading branch information
iago-lito committed Feb 9, 2024
1 parent 367760c commit edb8d19
Show file tree
Hide file tree
Showing 2 changed files with 185 additions and 127 deletions.
115 changes: 67 additions & 48 deletions src/AliasingDicts/nested_2D_api.jl
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
#
# o = 5 (in a :i context): means `data[:o][:i] = 5`
#
# A template of expected 'types' for every entry in this 2D structure can be provided.
# A template of expected types for every entry in this 2D structure can be provided.
#
# This file describes the associated parsing logic, guards and error handling.
#
Expand All @@ -27,7 +27,7 @@ aliaserr(mess) = throw(AliasingError(mess))

#-------------------------------------------------------------------------------------------
# Improve error messages by keeping track of the exact way arguments were input.
abstract type Nested2DArg{O,I} end # Parametrize with the aliased dict types.
abstract type Nested2DArg{O<:AliasingDict,I<:AliasingDict} end # Parametrize with the aliased dict type O{I{T}}.

for T in [
:BasalArg, # The two parts are explicitly given: `<o>_<i> = ..`.
Expand Down Expand Up @@ -244,18 +244,19 @@ function parse_2D_arguments(
) where {O<:AliasingDict,I<:AliasingDict}

# Start empty, fill as we collect and check arguments from input.
all_args = make_empty()
(inner_types, all_types) = types
all_args = make_empty(inner_types)

# Check whether a value has already been given.
already(outer, inner) = haskey(all_args[outer], inner)

# Record in result with ambiguity guard and type check.
record(arg, outer, inner, value) =
function record(arg, outer, inner, value)
if already(outer, inner)
(ex_arg, _) = all_args[outer][inner]
ambiguity(nest_name, arg, ex_arg)
else
T = types[outer][inner]
T = all_types[outer][inner]
value = try
convert(T, value)
catch
Expand All @@ -270,6 +271,7 @@ function parse_2D_arguments(
end
all_args[outer][inner] = (arg, value)
end
end

if isnothing(implicit_outer) && isnothing(implicit_inner)

Expand All @@ -285,18 +287,21 @@ function parse_2D_arguments(
found_nested = true
# Scroll sub-arguments within the nested specification.
# (better ask forgiveness than permission on this one)
if (
!applicable(keys, value) ||
(typeof(first(keys(value))) <: Integer) ||
!applicable(iterate, value)
)
iterr(e) = begin
fname = uppercasefirst(name(OuterDict))
nname = name(InnerDict)
argerr("$fname argument '$arg' \
cannot be iterated as ($nname=value,) pairs.")
cannot be iterated as ($nname=value,) pairs: $e")
end
for (nested_arg, val) in zip(keys(value), values(value))
z = try
zip(keys(value), values(value))
catch e
iterr(e)
end
for (nested_arg, val) in z
typeof(nested_arg) <: Integer && iterr("integer keys.")
outer, inner = ro(arg, nested_arg)
T = typeof(val)
nested = ArgType{O,I}(outer, inner)
record(nested, outer, inner, val)
end
Expand All @@ -320,6 +325,7 @@ function parse_2D_arguments(
# There may be several matches,
# but development ambiguity guards guarantee only one meaning.
((outer, inner, reversed),) = splits
T = typeof(value)
basal = (reversed ? RevBasalArg{O,I} : BasalArg{O,I})(outer, inner)
record(basal, outer, inner, value)

Expand All @@ -329,13 +335,15 @@ function parse_2D_arguments(
elseif isnothing(implicit_outer)
standardize(implicit_inner, I) # Guard against invalid implicit ref.
for (outer, value) in input
T = typeof(value)
arg = InnerContextArg{O,I}(outer, implicit_inner)
record(arg, outer, implicit_inner, value)
end

elseif isnothing(implicit_inner)
standardize(implicit_outer, O) # Guard agains invalid implicit ref.
for (inner, value) in input
T = typeof(value)
arg = OuterContextArg{O,I}(implicit_outer, inner)
record(arg, implicit_outer, inner, value)
end
Expand Down Expand Up @@ -375,8 +383,9 @@ function parse_outer_arguments_with_context(
implicit_inner = inner,
)
# Calculate common type in this context.
T = multi_promotion(types[o][inner] for o in standards(O))
res = O{Tuple{Nested2DArg,T}}()
(_, all_types) = types
T = multi_promotion(all_types[o][inner] for o in standards(O))
res = O{Tuple{Nested2DArg{O,I},T}}()
for (k, v) in all_args
haskey(v, inner) && (res[k] = v[inner])
end
Expand Down Expand Up @@ -444,26 +453,59 @@ macro prepare_2D_api(name, O, I)
O, I = ($O, $I)
types = $types

# Calculate common types per sub-dict.
inner_common_types = O{DataType}(
(k => $multi_promotion(values(sub)) for (k, sub) in $types)...,
)
types = (inner_common_types, types)

# Alias the basic nested (untracked) dict
# and specify an outer builder for it.
const $NestedDict{T} = O{I{T}}

# The values returned from arguments parsing
# also contains the original arguments input to improve error messages.
# Value possibly set to 'nothing' downstream.
const $Input = $Option{$Nested2DArg}
const $Input = $Option{$Nested2DArg{O,I}}
# Value + original input argument.
const $TrackedValue{T} = Tuple{$Input,T}
# All tracked internal values.
const $TrackedInnerDict{T} = I{$TrackedValue{T}}
# All tracked nested values.
const $Arguments = O{$TrackedInnerDict}
const $Arguments = O{I}

# Missing entries correspond to entries not specified as input.
# They can be filled with `nothing` values downstream.
# All entries start missing.
$make_empty() = $Arguments(
(
o => $TrackedInnerDict{$multi_promotion(values(types[o]))}() for
o in $standards(O)
)...,
)
$make_empty(inner_types) = begin
$Arguments(
(
o_key => $TrackedInnerDict{inner_types[o_key]}() for
o_key in $standards(O)
)...,
)
end

# Construct plain inner dict from tracked one.
function $I{T}(parsed::$TrackedInnerDict) where {T}
$I{T}((i => value for (i, (_, value)) in parsed)...)
end

# Parse directly from kwargs input.
function $NestedDict{T}(; kwargs...) where {T}
parsed = $parse(kwargs)
$NestedDict{T}(parsed)
end

# Remove arguments tracking information.
function $NestedDict{T}(parsed::$Arguments) where {T}
$NestedDict{T}(
(
o => I{T}((i => value for (i, (_, value)) in sub)...) for
(o, sub) in parsed
)...,
)
end

# Leave a hook to the check function to widen possible use cases.
$parse(
Expand All @@ -472,7 +514,7 @@ macro prepare_2D_api(name, O, I)
implicit_inner = nothing,
check = $check,
) = $parse_2D_arguments(
$types,
types,
$make_empty,
$name_sym,
check,
Expand All @@ -486,7 +528,7 @@ macro prepare_2D_api(name, O, I)
$parse_outer(inner, input; check = $check) =
$parse_outer_arguments_with_context(
inner,
$types,
types,
$make_empty,
$name_sym,
check,
Expand All @@ -498,7 +540,7 @@ macro prepare_2D_api(name, O, I)
$parse_inner(outer, input; check = $check) =
$parse_inner_arguments_with_context(
outer,
$types,
types,
$make_empty,
$name_sym,
check,
Expand All @@ -507,29 +549,6 @@ macro prepare_2D_api(name, O, I)
$I,
)

# Take this opportunity to also alias the basic nested (untracked) dict
# and specify an outer builder for it.
const $NestedDict{T} = O{I{T}}

# Parse directly from kwargs input.
function $NestedDict{T}(; kwargs...) where {T}
parsed = $parse(kwargs)
$NestedDict{T}(parsed)
end

# Remove arguments tracking information.
function $NestedDict{T}(parsed::$Arguments) where {T}
$NestedDict{T}(
(
o => I{T}((i => value for (i, (_, value)) in sub)...) for
(o, sub) in parsed
)...,
)
end
function $I{T}(parsed::$TrackedInnerDict) where {T}
$I{T}((i => value for (i, (_, value)) in parsed)...)
end

# Display for the nested dict (no need to name the two levels).
function AliasingDicts.display_short(d::$NestedDict)
(; display_short, shortest) = AliasingDicts
Expand Down
Loading

0 comments on commit edb8d19

Please sign in to comment.