Skip to content

Commit

Permalink
🚧 Fixed @conflicts macro tests..
Browse files Browse the repository at this point in the history
  • Loading branch information
iago-lito committed Sep 9, 2024
1 parent 68c9c72 commit 4161a17
Show file tree
Hide file tree
Showing 10 changed files with 338 additions and 187 deletions.
78 changes: 63 additions & 15 deletions src/Framework/add.jl
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ function check(
node::Node,
system::System,
brought::BroughtList,
checked::OrderedSet{<:CompType},
checked::OrderedDict{<:CompType,Node},
)

# Recursively check children first.
Expand All @@ -128,18 +128,42 @@ function check(
# Check against the current system value.
has_component(system, R) && continue
# Check against other components about to be brought.
any(C -> R <: C, checked) ||
any(C -> R <: C, keys(checked)) ||
throw(MissingRequiredComponent(requirer, R, node, reason))
end
end

# Guard against conflicts.
for (F, reason) in conflicts_(C)
has_component(system, F) &&
throw(ConflictWithSystemComponent(C, node, F, reason))
for Chk in checked
F <: typeof(Chk) &&
throw(ConflictWithBroughtComponent(C, node, brought[F], reason))
for (C_as, Other, reason) in all_conflicts(C)
if has_component(system, Other)
(Other, Other_abstract) =
isabstracttype(Other) ? (first(system._abstract[Other]), Other) :
(Other, nothing)
throw(
ConflictWithSystemComponent(
C,
C_as === C ? nothing : C_as,
node,
Other,
Other_abstract,
reason,
),
)
end
for Chk in keys(checked)
if Chk <: Other
throw(
ConflictWithBroughtComponent(
C,
C_as === C ? nothing : C_as,
node,
Chk,
Chk === Other ? nothing : Other,
checked[Chk],
reason,
),
)
end
end
end

Expand All @@ -154,7 +178,7 @@ function check(
end
end

push!(checked, C)
checked[C] = node
end

end
Expand All @@ -169,7 +193,7 @@ function add!(system::System{V}, blueprints::Blueprint{V}...) where {V}

forest = Node[]
brought = BroughtList{V}() # Populated during pre-order traversal.
checked = OrderedSet{CompType{V}}() # Populated during first post-order traversal.
checked = OrderedDict{CompType{V},Node}() # Populated during first post-order traversal.

#---------------------------------------------------------------------------------------
# Read-only preliminary checking.
Expand Down Expand Up @@ -212,7 +236,7 @@ function add!(system::System{V}, blueprints::Blueprint{V}...) where {V}
try

# Second post-order visit: expand the blueprints.
for Chk in checked
for Chk in keys(checked)
node = first(brought[Chk])
blueprint = node.blueprint

Expand Down Expand Up @@ -325,15 +349,20 @@ end

struct ConflictWithSystemComponent <: AddException
comp::CompType
comp_abstract::Option{CompType} # Fill if 'comp' conflicts as this abstract type.
node::Node
other::CompType
other_abstract::Option{CompType} # Fill if 'other' conflicts as this abstract type.
reason::Reason
end

struct ConflictWithBroughtComponent <: AddException
comp::CompType
comp_abstract::Option{CompType}
node::Node
other::Node
other::CompType
other_abstract::Option{CompType}
other_node::Node
reason::Reason
end

Expand Down Expand Up @@ -470,14 +499,33 @@ function Base.showerror(io::IO, e::UnexpectedHookFailure)
end

function Base.showerror(io::IO, e::ConflictWithSystemComponent)
(; comp, node, other, reason) = e
(; comp, comp_abstract, node, other, other_abstract, reason) = e
path = render_path(node)
header = "Blueprint would expand into $(typeof(comp)), \
which conflicts with $other already in the system"
comp_as = isnothing(comp_abstract) ? "" : " (as a $comp_abstract)"
other_as = isnothing(other_abstract) ? "" : " (as a $other_abstract)"
header = "Blueprint would expand into $comp, \
which$comp_as conflicts with $other$other_as already in the system"
if isnothing(reason)
body = "."
else
body = ":\n $reason"
end
print(io, "$header$body\n$path")
end

function Base.showerror(io::IO, e::ConflictWithBroughtComponent)
(; comp, comp_abstract, node, other, other_abstract, other_node, reason) = e
path = render_path(node)
other_path = render_path(other_node)
comp_as = isnothing(comp_abstract) ? "" : " (as a $comp_abstract)"
other_as = isnothing(other_abstract) ? "" : " (as a $other_abstract)"
header = "Blueprint would expand into $comp, \
which$comp_as would conflict with $other$other_as \
already brought by the same blueprint"
if isnothing(reason)
body = "."
else
body = ":\n $reason"
end
print(io, "$header$body\nAlready brought: $other_path---\n$path")
end
23 changes: 23 additions & 0 deletions src/Framework/component.jl
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,29 @@ function vertical_guard(A::CompType, B::CompType, err_same::Function, err_diff::
err_diff(sub, sup)
end

#-------------------------------------------------------------------------------------------
# The 'conflicts_' mapping entries are either abstract or concrete component,
# which makes checking information for one particular component not exactly straighforward.

# (for some reason this is absent from Base)
function supertypes(T::Type)
S = supertype(T)
S === T ? (T,) : (T, supertypes(S)...)
end

# Iterate over all conflicts for one particular component.
# yields (conflict_key, conflicting_component, reason)
# The yielded conflict key may be a supercomponent of the focal one.
function all_conflicts(C::CompType)
supers = ifilter(T -> T !== Any, supertypes(C))
Iterators.flatten(imap(supers) do Sup
entries = conflicts_(Sup)
imap(entries) do (k, v)
(Sup, k, v)
end
end)
end

# ==========================================================================================
# Display.

Expand Down
88 changes: 1 addition & 87 deletions src/Framework/component_macro.jl
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,7 @@ function component_macro(__module__, __source__, input...)
SuperComponent =
$(tovalue(super, "Evaluating given supercomponent", DataType))
if !(SuperComponent <: Component)
xerr(
"Supercomponent: '$SuperComponent' does not subtype '$Component'.",
)
xerr("Supercomponent: $SuperComponent does not subtype $Component.")
end
ValueType = system_value_type(SuperComponent)
end,
Expand Down Expand Up @@ -320,87 +318,3 @@ function component_macro(__module__, __source__, input...)

res
end

#-------------------------------------------------------------------------------------------
# The 'conflicts_' mapping entries are either abstract or concrete component,
# which makes checking information for one particular component not exactly straighforward.

# (for some reason this is absent from Base)
function supertypes(T::Type)
S = supertype(T)
S === T ? (T,) : (T, supertypes(S)...)
end

# Iterate over all conflicting entries with the given component or a supercomponent of it.
super_conflict_keys(C::CompType) =
Iterators.filter(supertypes(C)) do sup
conflicts_(sup)
end

# Iterate over all conflicts for one particular component.
# yields (conflict_key, conflicting_component, reason)
# The yielded conflict key may be a supercomponent of the focal one.
function all_conflicts(C::CompType)
Iterators.flatten(Iterators.map(super_conflict_keys(C)) do key
Iterators.map(conflicts_(key)) do (conflicting, reason)
(key, conflicting, reason)
end
end)
end

# Guard against declaring conflicts between sub/super components.
function vertical_conflict(err)
(sub, sup) -> begin
it = sub === sup ? "itself" : "its own super-component '$sup'"
err("Component '$sub' cannot conflict with $it.")
end
end

# Declare one particular conflict with a reason.
# Guard against redundant reasons specifications.
function declare_conflict(A::CompType, B::CompType, reason::Reason, err)
vertical_guard(A, B, vertical_conflict(err))
for (k, c, reason) in all_conflicts(A)
isnothing(reason) && continue
if B <: c
as_K = k === A ? "" : " (as '$k')"
as_C = B === c ? "" : " (as '$c')"
err("Component '$A'$as_K already declared to conflict with '$B'$as_C \
for the following reason:\n $(reason)")
end
end
# Append new method or override by updating value.
current = conflicts_(A) # Creates a new empty value if falling back on default impl.
if isempty(current)
# Dynamically add method to lend reference to the value lended by `conflicts_`.
eval(quote
conflicts_(::Type{$A}) = $current
end)
end
current[B] = reason
end

# Fill up a clique, not overriding any existing reason.
function declare_conflicts_clique(err, components::Vector{<:CompType{V}}) where {V}

function process_pair(A::CompType{V}, B::CompType{V})
vertical_guard(A, B, vertical_conflict(err))
# Same logic as above.
current = conflicts_(A)
if isempty(current)
eval(quote
conflicts_(::Type{$A}) = $current
end)
end
haskey(current, B) || (current[B] = nothing)
end

# Triangular-iterate to guard against redundant items.
for (i, a) in enumerate(components)
for b in components[1:(i-1)]
process_pair(a, b)
process_pair(b, a)
end
end

end
75 changes: 75 additions & 0 deletions src/Framework/conflicts_macro.jl
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,78 @@ function conflicts_macro(__module__, __source__, input...)
res

end

# Guard against declaring conflicts between sub/super components.
function vertical_conflict(err)
(sub, sup) -> begin
it = sub === sup ? "itself" : "its own super-component $sup"
err("Component $sub cannot conflict with $it.")
end
end

# Declare one particular conflict with a reason.
# Guard against redundant reasons specifications.
function declare_conflict(A::CompType, B::CompType, reason::Reason, err)
vertical_guard(A, B, vertical_conflict(err))
for (k, c, reason) in all_conflicts(A)
isnothing(reason) && continue
if B <: c
as_K = k === A ? "" : " (as $k)"
as_C = B === c ? "" : " (as $c)"
err("Component $A$as_K already declared to conflict with $B$as_C \
for the following reason:\n $(reason)")
end
end
# Append new method or override by updating value.
current = conflicts_(A) # Creates a new empty value if falling back on default impl.
if isempty(current)
# Dynamically add method to lend reference to the value lended by `conflicts_`.
eval(quote
conflicts_(::Type{$A}) = $current
end)
end
current[B] = reason
end

# Fill up a clique, not overriding any existing reason.
function declare_conflicts_clique(err, components::Vector{<:CompType{V}}) where {V}

# The result of overriding methods like the above
# will not be visible from within the same function call
# because of <mumblemumblejuliaworldcount>.
# So, collect all required overrides in this collection
# to perform them only once at the end.

changes = Dict{CompType{V},Tuple{Bool,Any}}() # {Component: (needs_override, NewConflictsDict)}

function process_pair(A::CompType{V}, B::CompType{V})
vertical_guard(A, B, vertical_conflict(err))
current = if haskey(changes, A)
_, current = changes[A]
current
else
current = conflicts_(A)
changes[A] = (isempty(current), current)
current
end
haskey(current, B) || (current[B] = nothing)
end

# Triangular-iterate to guard against redundant items.
for (i, a) in enumerate(components)
for b in components[1:(i-1)]
process_pair(a, b)
process_pair(b, a)
end
end

# Perform all the overrides at once.
for (C, (needs_override, conflicts)) in changes
if needs_override
eval(quote
conflicts_(::Type{$C}) = $conflicts
end)
end
end

end
14 changes: 7 additions & 7 deletions src/Framework/macro_helpers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -103,15 +103,15 @@ function to_component(mod, xp, value_type_var, ctx, xerr)
if C isa Type
if !(C <: Sup)
but = C <: Component ? ", but '$(Component{system_value_type(C)})'" : ""
$xerr("$($ctx): the given type \
does not subtype '$Sup'$but:$(xpres($qxp, C))")
$xerr("$($ctx): expression does not evaluate \
to a subtype of $Sup$but:$(xpres($qxp, C))")
end
C
else
c = C # Actually an instance.
if !(c isa Sup)
but = c isa Component ? ", but for '$(system_value_type(c))'" : ""
$xerr("$($ctx): the given expression does not evaluate \
$xerr("$($ctx): expression does not evaluate \
to a component for '$($value_type_var)'$but:$(xpres($qxp, c))")
end
typeof(c)
Expand All @@ -126,13 +126,13 @@ function to_component(mod, xp, ctx, xerr)
C = $(to_value(mod, xp, ctx, xerr, Any))
# Don't check the system value type, but infer it.
if C isa Type
C <: Component || $xerr("$($ctx): the given type \
does not subtype $Component:$(xpres($qxp, C))")
C <: Component || $xerr("$($ctx): expression does not evaluate \
to a subtype of $Component:$(xpres($qxp, C))")
C
else
c = C # Actually an instance.
c isa Component || $xerr("$($ctx): the given value \
is not a component:$(xpres($qxp, c))")
c isa Component || $xerr("$($ctx): expression does not evaluate \
to a component:$(xpres($qxp, c))")
typeof(c)
end
end
Expand Down
1 change: 1 addition & 0 deletions test/dedicated_test_failures/framework.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import EcologicalNetworksDynamics.Framework:
ConflictMacroExecError,
ConflictMacroParseError,
ConflictWithSystemComponent,
ConflictWithBroughtComponent,
Framework,
HookCheckFailure,
ItemMacroExecError,
Expand Down
Loading

0 comments on commit 4161a17

Please sign in to comment.