com.sageserpent.kineticmerge.core.MatchesContext.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kinetic-merge_3 Show documentation
Show all versions of kinetic-merge_3 Show documentation
Merge branches in the presence of code motion within and between files.
The newest version!
package com.sageserpent.kineticmerge.core
import com.sageserpent.kineticmerge.core.Match.*
import com.sageserpent.kineticmerge.core.merge.MergeAlgebra
import com.sageserpent.kineticmerge.mergeByKeyWith
import com.typesafe.scalalogging.StrictLogging
import monocle.syntax.all.*
import scala.collection.immutable.MultiDict
class MatchesContext[Element](
matchesFor: Element => collection.Set[Match[Element]]
):
val emptyReport: MoveDestinationsReport = MoveDestinationsReport(Map.empty)
private def sourcesOf(element: Element): collection.Set[Element] =
matchesFor(element).flatMap {
case BaseAndLeft(baseElement, _) => Some(baseElement)
case BaseAndRight(baseElement, _) => Some(baseElement)
case LeftAndRight(_, _) => None
case AllSides(baseElement, _, _) => Some(baseElement)
}
/** @param coreMergeResult
* What is says on the tin: a simpler merge result that is delegated to by
* the operations implemented in
* [[MatchesContext.MergeResultDetectingMotion.mergeAlgebra]].
* @param changesMigratedThroughMotion
* Edits and deletions to be migrated, referenced by move destination.
* @param moveDestinationsReport
* @param insertions
* Insertions that may need to be migrated - in contrast to
* {@code changesMigratedThroughMotion} , these have to be collected
* speculatively upfront and then associated with anchors once the global
* picture of code motion is available.
* @param oneSidedDeletions
* Deletions on just one side that may influence the discovery of anchors
* for insertion migration.
* @tparam CoreResult
* @tparam Element
*/
case class MergeResultDetectingMotion[CoreResult[_], Element](
coreMergeResult: CoreResult[Element],
changesMigratedThroughMotion: MultiDict[Element, IndexedSeq[Element]],
moveDestinationsReport: MoveDestinationsReport,
insertions: Seq[Insertion],
oneSidedDeletions: Set[Element]
)
// NOTE: this could be moved into a companion object for `MatchesContext`, but
// we still need to define `MoveDestinationsReport` in scope of the class,
// thus requiring an import from the companion *and* an instance of the class
// itself. It is possible to define `MoveDestinationsReport` as a trait in the
// companion and thus to move `MergeResultDetectingMotion` into the companion
// too, but I'm not sure that it makes things more readable elsewhere. We also
// want to merge instances of `MoveDestinationsReport` that share the same
// `MatchesContext`. All in all, let's stick with the current design for
// now...
enum Side:
case Left
case Right
end Side
case class Insertion(side: Side, inserted: Element)
case class MoveDestinationsReport(
moveDestinationsByMatches: Map[collection.Set[
Match[Element]
], MoveDestinations[Element]]
):
def leftMoveOf(
element: Element
): MoveDestinationsReport =
val matches = matchesFor(element)
if matches.nonEmpty then
MoveDestinationsReport(
moveDestinationsByMatches.updatedWith(matches) {
case None =>
Some(
MoveDestinations(
sources = sourcesOf(element),
left = Set(element),
right = Set.empty,
coincident = Set.empty
)
)
case Some(moveDestinations) =>
Some(moveDestinations.focus(_.left).modify(_ + element))
}
)
else this
end if
end leftMoveOf
def rightMoveOf(
element: Element
): MoveDestinationsReport =
val matches = matchesFor(element)
if matches.nonEmpty then
MoveDestinationsReport(
moveDestinationsByMatches.updatedWith(matches) {
case None =>
Some(
MoveDestinations(
sources = sourcesOf(element),
left = Set.empty,
right = Set(element),
coincident = Set.empty
)
)
case Some(moveDestinations) =>
Some(moveDestinations.focus(_.right).modify(_ + element))
}
)
else this
end if
end rightMoveOf
def coincidentMoveOf(
elementPairAcrossLeftAndRight: (Element, Element)
): MoveDestinationsReport =
val matches = matchesFor(elementPairAcrossLeftAndRight._1)
if matches.nonEmpty then
MoveDestinationsReport(
moveDestinationsByMatches.updatedWith(matches) {
case None =>
Some(
MoveDestinations(
sources = sourcesOf(elementPairAcrossLeftAndRight._1),
left = Set.empty,
right = Set.empty,
coincident = Set(elementPairAcrossLeftAndRight)
)
)
case Some(moveDestinations) =>
Some(
moveDestinations
.focus(_.coincident)
.modify(_ + elementPairAcrossLeftAndRight)
)
}
)
else this
end if
end coincidentMoveOf
def mergeWith(another: MoveDestinationsReport): MoveDestinationsReport =
MoveDestinationsReport(
this.moveDestinationsByMatches.mergeByKeyWith(
another.moveDestinationsByMatches
) {
case (Some(lhs), Some(rhs)) => lhs mergeWith rhs
case (Some(lhs), None) => lhs
case (None, Some(rhs)) => rhs
}
)
def summarizeInText: Iterable[String] =
moveDestinationsByMatches.values.map(_.description)
end MoveDestinationsReport
object MergeResultDetectingMotion extends StrictLogging:
private type MergeResultDetectingMotionType[CoreResult[_]] =
[Element] =>> MergeResultDetectingMotion[CoreResult, Element]
def mergeAlgebra[CoreResult[_]](
coreMergeAlgebra: merge.MergeAlgebra[CoreResult, Element],
resolution: Resolution[Element]
): MergeAlgebra[MergeResultDetectingMotionType[CoreResult], Element] =
type ConfiguredMergeResultDetectingMotion[Element] =
MergeResultDetectingMotionType[CoreResult][Element]
new MergeAlgebra[MergeResultDetectingMotionType[CoreResult], Element]:
override def empty: ConfiguredMergeResultDetectingMotion[Element] =
MergeResultDetectingMotion(
coreMergeResult = coreMergeAlgebra.empty,
changesMigratedThroughMotion = MultiDict.empty,
moveDestinationsReport = emptyReport,
insertions = Vector.empty,
oneSidedDeletions = Set.empty
)
override def preservation(
result: MergeResultDetectingMotionType[CoreResult][Element],
preservedBaseElement: Element,
preservedElementOnLeft: Element,
preservedElementOnRight: Element
): ConfiguredMergeResultDetectingMotion[Element] = result
.focus(_.coreMergeResult)
.modify((result: CoreResult[Element]) =>
coreMergeAlgebra.preservation(
result,
preservedBaseElement,
preservedElementOnLeft,
preservedElementOnRight
)
)
override def leftInsertion(
result: ConfiguredMergeResultDetectingMotion[Element],
insertedElement: Element
): ConfiguredMergeResultDetectingMotion[Element] =
result
.focus(_.insertions)
.modify(_ :+ Insertion(Side.Left, insertedElement))
.focus(_.coreMergeResult)
.modify(coreMergeAlgebra.leftInsertion(_, insertedElement))
.focus(_.moveDestinationsReport)
.modify(_.leftMoveOf(insertedElement))
end leftInsertion
override def rightInsertion(
result: ConfiguredMergeResultDetectingMotion[Element],
insertedElement: Element
): ConfiguredMergeResultDetectingMotion[Element] =
result
.focus(_.insertions)
.modify(_ :+ Insertion(Side.Right, insertedElement))
.focus(_.coreMergeResult)
.modify(coreMergeAlgebra.rightInsertion(_, insertedElement))
.focus(_.moveDestinationsReport)
.modify(_.rightMoveOf(insertedElement))
override def coincidentInsertion(
result: MergeResultDetectingMotionType[CoreResult][Element],
insertedElementOnLeft: Element,
insertedElementOnRight: Element
): ConfiguredMergeResultDetectingMotion[Element] = result
.focus(_.coreMergeResult)
.modify((result: CoreResult[Element]) =>
coreMergeAlgebra.coincidentInsertion(
result,
insertedElementOnLeft,
insertedElementOnRight
)
)
.focus(_.moveDestinationsReport)
.modify(
_.coincidentMoveOf(insertedElementOnLeft -> insertedElementOnRight)
)
override def leftDeletion(
result: ConfiguredMergeResultDetectingMotion[Element],
deletedBaseElement: Element,
deletedRightElement: Element
): ConfiguredMergeResultDetectingMotion[Element] =
def default = result
.focus(_.coreMergeResult)
.modify(
coreMergeAlgebra
.leftDeletion(_, deletedBaseElement, deletedRightElement)
)
.focus(_.oneSidedDeletions)
.modify(_ + deletedRightElement)
val matches = matchesFor(deletedBaseElement).toSeq
matches match
case Seq(_: AllSides[Section[Element]], _*) =>
matches.foldLeft(default) {
case (
partialResult,
AllSides(_, leftElementAtMoveDestination, rightElement)
) if deletedRightElement == rightElement =>
val resolved = resolution(
Some(deletedBaseElement),
leftElementAtMoveDestination,
deletedRightElement
)
if resolved != leftElementAtMoveDestination then
logger.debug(
s"Left deletion at origin of move: also migrating additional minor edit resolution ${pprintCustomised(resolved)} to left move destination ${pprintCustomised(leftElementAtMoveDestination)}."
)
partialResult
.focus(_.changesMigratedThroughMotion)
.modify(
_ + (leftElementAtMoveDestination -> IndexedSeq(
resolved
))
)
else partialResult
end if
case (partialResult, _) => partialResult
}
case _ => default
end match
end leftDeletion
override def rightDeletion(
result: ConfiguredMergeResultDetectingMotion[Element],
deletedBaseElement: Element,
deletedLeftElement: Element
): ConfiguredMergeResultDetectingMotion[Element] =
def default = result
.focus(_.coreMergeResult)
.modify(
coreMergeAlgebra
.rightDeletion(_, deletedBaseElement, deletedLeftElement)
)
.focus(_.oneSidedDeletions)
.modify(_ + deletedLeftElement)
val matches = matchesFor(deletedBaseElement).toSeq
matches match
case Seq(_: AllSides[Section[Element]], _*) =>
matches.foldLeft(default) {
case (
partialResult,
AllSides(_, leftElement, rightElementAtMoveDestination)
) if deletedLeftElement == leftElement =>
val resolved = resolution(
Some(deletedBaseElement),
deletedLeftElement,
rightElementAtMoveDestination
)
if resolved != rightElementAtMoveDestination then
logger.debug(
s"Right deletion at origin of move: also migrating additional minor edit resolution ${pprintCustomised(resolved)} to right move destination ${pprintCustomised(rightElementAtMoveDestination)}."
)
partialResult
.focus(_.changesMigratedThroughMotion)
.modify(
_ + (rightElementAtMoveDestination -> IndexedSeq(
resolved
))
)
else partialResult
end if
case (partialResult, _) => partialResult
}
case _ => default
end match
end rightDeletion
override def coincidentDeletion(
result: ConfiguredMergeResultDetectingMotion[Element],
deletedElement: Element
): ConfiguredMergeResultDetectingMotion[Element] =
def default = result
.focus(_.coreMergeResult)
.modify(coreMergeAlgebra.coincidentDeletion(_, deletedElement))
val matches = matchesFor(deletedElement).toSeq
matches match
case Seq(_: AllSides[Section[Element]], _*) =>
logger.debug(
s"Coincident deletion at origin of moves on both sides."
)
default
case Seq(_: BaseAndLeft[Section[Element]], _*) =>
matches.foldLeft(default) {
case (result, BaseAndLeft(_, leftElementAtMoveDestination)) =>
logger.debug(
s"Coincident deletion at origin of move: migrating deletion to left move destination ${pprintCustomised(leftElementAtMoveDestination)}."
)
result
.focus(_.changesMigratedThroughMotion)
.modify(
_ + (leftElementAtMoveDestination -> IndexedSeq.empty)
)
}
case Seq(_: BaseAndRight[Section[Element]], _*) =>
matches.foldLeft(default) {
case (result, BaseAndRight(_, rightElementAtMoveDestination)) =>
logger.debug(
s"Coincident deletion at origin of move: migrating deletion to right move destination ${pprintCustomised(rightElementAtMoveDestination)}."
)
result
.focus(_.changesMigratedThroughMotion)
.modify(
_ + (rightElementAtMoveDestination -> IndexedSeq.empty)
)
}
case Seq() => default
end match
end coincidentDeletion
override def leftEdit(
result: ConfiguredMergeResultDetectingMotion[Element],
editedBaseElement: Element,
editedRightElement: Element,
editElements: IndexedSeq[Element]
): ConfiguredMergeResultDetectingMotion[Element] =
def default = result
.focus(_.coreMergeResult)
.modify(
coreMergeAlgebra
.leftEdit(
_,
editedBaseElement,
editedRightElement,
editElements
)
)
.focus(_.moveDestinationsReport)
.modify(editElements.foldLeft(_)(_ leftMoveOf _))
val matches = matchesFor(editedBaseElement).toSeq
matches match
case Seq(_: AllSides[Section[Element]], _*) =>
matches.foldLeft(default) {
case (
partialResult,
AllSides(_, leftElementAtMoveDestination, rightElement)
) if editedRightElement == rightElement =>
val resolved = resolution(
Some(editedBaseElement),
leftElementAtMoveDestination,
editedRightElement
)
if resolved != leftElementAtMoveDestination then
logger.debug(
s"Left edit at origin of move: also migrating additional minor edit resolution ${pprintCustomised(resolved)} to left move destination ${pprintCustomised(leftElementAtMoveDestination)}."
)
partialResult
.focus(_.changesMigratedThroughMotion)
.modify(
_ + (leftElementAtMoveDestination -> IndexedSeq(
resolved
))
)
else partialResult
end if
case (partialResult, _) => partialResult
}
case _ => default
end match
end leftEdit
override def rightEdit(
result: ConfiguredMergeResultDetectingMotion[Element],
editedBaseElement: Element,
editedLeftElement: Element,
editElements: IndexedSeq[Element]
): ConfiguredMergeResultDetectingMotion[Element] =
def default = result
.focus(_.coreMergeResult)
.modify(
coreMergeAlgebra
.rightEdit(
_,
editedBaseElement,
editedLeftElement,
editElements
)
)
.focus(_.moveDestinationsReport)
.modify(editElements.foldLeft(_)(_ rightMoveOf _))
val matches = matchesFor(editedBaseElement).toSeq
matches match
case Seq(_: AllSides[Section[Element]], _*) =>
matches.foldLeft(default) {
case (
partialResult,
AllSides(_, leftElement, rightElementAtMoveDestination)
) if editedLeftElement == leftElement =>
val resolved = resolution(
Some(editedBaseElement),
editedLeftElement,
rightElementAtMoveDestination
)
if resolved != rightElementAtMoveDestination then
logger.debug(
s"Right edit at origin of move: also migrating additional minor edit resolution ${pprintCustomised(resolved)} to right move destination ${pprintCustomised(rightElementAtMoveDestination)}."
)
partialResult
.focus(_.changesMigratedThroughMotion)
.modify(
_ + (rightElementAtMoveDestination -> IndexedSeq(
resolved
))
)
else partialResult
end if
case (partialResult, _) => partialResult
}
case _ => default
end match
end rightEdit
override def coincidentEdit(
result: ConfiguredMergeResultDetectingMotion[Element],
editedElement: Element,
editElements: IndexedSeq[(Element, Element)]
): ConfiguredMergeResultDetectingMotion[Element] =
def default = result
.focus(_.coreMergeResult)
.modify(
coreMergeAlgebra.coincidentEdit(_, editedElement, editElements)
)
.focus(_.moveDestinationsReport)
.modify(editElements.foldLeft(_)(_ coincidentMoveOf _))
val matches = matchesFor(editedElement).toSeq
matches match
// NOTE: we're not missing the all-sides case below - the default
// handles it perfectly well, as the left and right contributions to
// the match are *incoming* moves, so there is nothing to migrate.
case Seq(_: BaseAndLeft[Section[Element]], _*) =>
matches.foldLeft(default) {
case (result, BaseAndLeft(_, leftElementAtMoveDestination)) =>
logger.debug(
s"Coincident edit at origin of move: migrating deletion to left move destination ${pprintCustomised(leftElementAtMoveDestination)}."
)
result
.focus(_.changesMigratedThroughMotion)
.modify(
_ + (leftElementAtMoveDestination -> IndexedSeq.empty)
)
}
case Seq(_: BaseAndRight[Section[Element]], _*) =>
matches.foldLeft(default) {
case (result, BaseAndRight(_, rightElementAtMoveDestination)) =>
logger.debug(
s"Coincident edit at origin of move: migrating deletion to right move destination ${pprintCustomised(rightElementAtMoveDestination)}."
)
result
.focus(_.changesMigratedThroughMotion)
.modify(
_ + (rightElementAtMoveDestination -> IndexedSeq.empty)
)
}
case Seq() =>
default
end match
end coincidentEdit
override def conflict(
result: ConfiguredMergeResultDetectingMotion[Element],
editedElements: IndexedSeq[Element],
leftEditElements: IndexedSeq[Element],
rightEditElements: IndexedSeq[Element]
): ConfiguredMergeResultDetectingMotion[Element] =
val insertionConflict =
editedElements.isEmpty && leftEditElements.nonEmpty && rightEditElements.nonEmpty
val conflictInvolvingAnEdit =
editedElements.nonEmpty && (leftEditElements.nonEmpty || rightEditElements.nonEmpty)
require(
insertionConflict || conflictInvolvingAnEdit
)
def default =
result
.focus(_.coreMergeResult)
.modify(
coreMergeAlgebra.conflict(
_,
editedElements,
leftEditElements,
rightEditElements
)
)
.focus(_.moveDestinationsReport)
.modify(leftEditElements.foldLeft(_)(_ leftMoveOf _))
.focus(_.moveDestinationsReport)
.modify(rightEditElements.foldLeft(_)(_ rightMoveOf _))
(editedElements, leftEditElements, rightEditElements) match
case (Seq(baseElement), leftElements @ Seq(_, _*), Seq()) =>
// Left edit versus right deletion conflict...
val matches = matchesFor(baseElement).toSeq
matches match
case Seq(_: AllSides[Section[Element]], _*) =>
val withCoincidentDeletion = result
.focus(_.coreMergeResult)
.modify(
coreMergeAlgebra
.coincidentDeletion(_, baseElement)
)
val withLeftInsertions = leftElements.foldLeft(
withCoincidentDeletion
)(leftInsertion)
logger.debug(
s"Conflict at origin of moves on both sides: resolved as a coincident deletion of ${pprintCustomised(baseElement)} and a left insertion of ${pprintCustomised(leftElements)}."
)
withLeftInsertions
case Seq(_: BaseAndRight[Section[Element]], _*) =>
val withCoincidentDeletion = result
.focus(_.coreMergeResult)
.modify(
coreMergeAlgebra.coincidentDeletion(_, baseElement)
)
matches.foldLeft(withCoincidentDeletion) {
case (
result,
BaseAndRight(_, rightElementAtMoveDestination)
) =>
logger.debug(
s"Conflict at origin of move: resolved as a coincident deletion of ${pprintCustomised(baseElement)}; migrating left edit ${pprintCustomised(leftElements)} to right move destination ${pprintCustomised(rightElementAtMoveDestination)}."
)
result
.focus(_.changesMigratedThroughMotion)
.modify(
_ + (rightElementAtMoveDestination -> leftElements)
)
}
case Seq(_: BaseAndLeft[Section[Element]], _*) =>
val withCoincidentDeletion = result
.focus(_.coreMergeResult)
.modify(
coreMergeAlgebra
.coincidentDeletion(_, baseElement)
)
val withLeftInsertions = leftElements.foldLeft(
withCoincidentDeletion
)(leftInsertion)
matches.foldLeft(withLeftInsertions) {
case (
partialResult,
BaseAndLeft(_, leftElementAtMoveDestination)
) =>
logger.debug(
s"Conflict at origin of move: resolved as a coincident deletion of ${pprintCustomised(baseElement)} and a left insertion of ${pprintCustomised(leftElements)}; migrating deletion to left move destination ${pprintCustomised(leftElementAtMoveDestination)}."
)
partialResult
.focus(_.changesMigratedThroughMotion)
.modify(
_ + (leftElementAtMoveDestination -> IndexedSeq.empty)
)
}
case Seq() => default
end match
case (Seq(baseElement), Seq(), rightElements @ Seq(_, _*)) =>
// Left deletion versus right edit conflict...
val matches = matchesFor(baseElement).toSeq
matches match
case Seq(_: AllSides[Section[Element]], _*) =>
val withCoincidentDeletion = result
.focus(_.coreMergeResult)
.modify(
coreMergeAlgebra
.coincidentDeletion(_, baseElement)
)
val withRightInsertions = rightElements.foldLeft(
withCoincidentDeletion
)(rightInsertion)
logger.debug(
s"Conflict at origin of moves on both sides: resolved as a coincident deletion of ${pprintCustomised(baseElement)} and a right insertion of ${pprintCustomised(rightElements)}."
)
withRightInsertions
case Seq(_: BaseAndLeft[Section[Element]], _*) =>
val withCoincidentDeletion = result
.focus(_.coreMergeResult)
.modify(
coreMergeAlgebra.coincidentDeletion(_, baseElement)
)
matches.foldLeft(
withCoincidentDeletion
) {
case (
result,
BaseAndLeft(_, leftElementAtMoveDestination)
) =>
logger.debug(
s"Conflict at origin of move: resolved as a coincident deletion of ${pprintCustomised(baseElement)}; migrating right edit ${pprintCustomised(rightElements)} to left move destination ${pprintCustomised(leftElementAtMoveDestination)}."
)
result
.focus(_.changesMigratedThroughMotion)
.modify(
_ + (leftElementAtMoveDestination -> rightElements)
)
}
case Seq(_: BaseAndRight[Section[Element]], _*) =>
val withCoincidentDeletion = result
.focus(_.coreMergeResult)
.modify(
coreMergeAlgebra
.coincidentDeletion(_, baseElement)
)
val withRightInsertions = rightElements.foldLeft(
withCoincidentDeletion
)(rightInsertion)
matches.foldLeft(withRightInsertions) {
case (
partialResult,
BaseAndRight(_, rightElementAtMoveDestination)
) =>
logger.debug(
s"Conflict at origin of move: resolved as a coincident deletion of ${pprintCustomised(baseElement)} and a right insertion of ${pprintCustomised(rightElements)}; migrating deletion to right move destination ${pprintCustomised(rightElementAtMoveDestination)}."
)
partialResult
.focus(_.changesMigratedThroughMotion)
.modify(
_ + (rightElementAtMoveDestination -> IndexedSeq.empty)
)
}
case Seq() => default
end match
case (
Seq(baseElement),
leftElements @ Seq(_, _*),
rightElements @ Seq(_, _*)
) =>
// Left edit versus right edit conflict...
val matches = matchesFor(baseElement).toSeq
matches match
case Seq(_: AllSides[Section[Element]], _*) =>
default
case Seq(_: BaseAndLeft[Section[Element]], _*) =>
val withCoincidentDeletion = result
.focus(_.coreMergeResult)
.modify(
coreMergeAlgebra
.coincidentDeletion(_, baseElement)
)
val withLeftInsertions = leftElements.foldLeft(
withCoincidentDeletion
)(leftInsertion)
matches.foldLeft(withLeftInsertions) {
case (
partialResult,
BaseAndLeft(_, leftElementAtMoveDestination)
) =>
logger.debug(
s"Conflict at origin of move: resolved as a coincident deletion of ${pprintCustomised(baseElement)} and a left insertion of ${pprintCustomised(leftElements)}; migrating right edit ${pprintCustomised(rightElements)} to left move destination ${pprintCustomised(leftElementAtMoveDestination)}."
)
partialResult
.focus(_.changesMigratedThroughMotion)
.modify(
_ + (leftElementAtMoveDestination -> rightElements)
)
}
case Seq(_: BaseAndRight[Section[Element]], _*) =>
val withCoincidentDeletion = result
.focus(_.coreMergeResult)
.modify(
coreMergeAlgebra
.coincidentDeletion(_, baseElement)
)
val withRightInsertions = rightElements.foldLeft(
withCoincidentDeletion
)(rightInsertion)
matches.foldLeft(withRightInsertions) {
case (
partialResult,
BaseAndRight(_, rightElementAtMoveDestination)
) =>
logger.debug(
s"Conflict at origin of move: resolved as a coincident deletion of ${pprintCustomised(baseElement)} and a right insertion of ${pprintCustomised(rightElements)}; migrating left edit ${pprintCustomised(leftElements)} to right move destination ${pprintCustomised(rightElementAtMoveDestination)}."
)
partialResult
.focus(_.changesMigratedThroughMotion)
.modify(
_ + (rightElementAtMoveDestination -> leftElements)
)
}
case Seq() => default
end match
case (
Seq(),
Seq(_, _*),
Seq(_, _*)
) =>
// Left insertion versus right insertion conflict...
default
end match
end conflict
end new
end mergeAlgebra
end MergeResultDetectingMotion
end MatchesContext
© 2015 - 2025 Weber Informatics LLC | Privacy Policy