Skip to content

Composition of incomplete mappings leads to weird behavior in complete #51

@dimitriye98

Description

@dimitriye98

Not sure whether this is arguably more a Mercury issue, but all the code in question is in Lorenz, so posting it here.

If you have a mapping set ab which exhaustively lists all mappings including overridden methods, and a mapping set bc which is intended to chain from state b but doesn't list overridden methods instead relying on complete to infer them, the composition of the mappings, ab.merge(bc) will never complete those methods, instead leaving them in their original state as in ab.

For example, if we have class AImpl extends A where both have method a() and the mapping sets are as follows

ab:

A.a() -> B.b()
AImpl.a() -> BImpl.b()

bc:

B.b() -> C.c()

the result is

A.a() -> C.c()
AImpl.a() -> BImpl.b()

and completion will never properly complete the mapping.

Not sure what the solution here is. In my project using the library, I used a workaround where I modified complete to use put instead of putIfAbsent and basically treat the highest mapping in the class hierarchy as canonical, which worked for my purposes, but ideally you'd want the most complete mapping to dominate, regardless of where it is in the hierarchy.

While making more complete mappings "infectious" in a way which climbs up the hierarchy would require significant restructuring of the code, and may not really be necessary as it's a bit of an edge case, for incorporation into the upstream I think a solution which at very least will not override a more complete mapping with a less complete one is necessary. (Though, that's a tradeoff in and of itself, in that arguably it's not ideal for application of mappings to change the semantics of the code, and it may be preferable for a less complete mapping to override a more complete one rather than for application of the mappings to make a method no longer override a parent's method.)

The parenthesized concern aside, such a solution would more or less necessitate keeping track of the history of the mapping mergers for each mapping, and inheriting the parent mapping only if it's "longer" (i.e. more composition steps). It also raises questions regarding complicated topologies that can emerge when matching multiple mapping sets.

E.g. if we have classes class AImpl extends A where both have method a(), and the user merges ab.merge(bc).merge(ad) resulting in a mapping set with both

A.a() -> B.b() -> C.c()
AImpl.a() -> DImpl.d()

Should A.a() -> C.c() override AImpl.a() -> DImpl.d()? That chain is longer, but on the other hand it doesn't actually continue the latter mapping.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions