All Downloads are FREE. Search and download functionalities are using the official Maven repository.

it.unich.scalafix.Combo.scala Maven / Gradle / Ivy

There is a newer version: 0.10.0
Show newest version
/**
 * Copyright 2015, 2016, 2021, 2022 Gianluca Amato  and
 * Francesca Scozzari 
 *
 * This file is part of ScalaFix. ScalaFix is free software: you can
 * redistribute it and/or modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * ScalaFix is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of a MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * ScalaFix. If not, see .
 */

package it.unich.scalafix

import it.unich.scalafix.utils.Domain

/**
 * A Combo is a way to combine two values into a new one. It is a specialization
 * of the functional type `(V,V) => V`, where the first parameter is the old
 * value of an unknown and the second parameter is the new contribution. Both
 * widenings and narrowings are examples of combos. Combos are mutable, i.e.,
 * the apply method may give different results for the same input when called
 * multiple times.
 *
 * Another function of combos is to be blueprints for building other equivalent
 * combos. Each combo has a copy method which should produce a functionally
 * equivalent copy of `this`. The copy method should try to minimize object
 * duplication.
 *
 * For the definition and examples of combos see:
 *   - Cousot, P., Cousot, R. Comparing the galois connection and
 *     widening/narrowing approaches to abstract interpretation Lecture Notes in
 *     Computer Science, 631 LNCS, pp. 269-295. 1992
 *   - Amato, G., Scozzari, F., Seidl, H., Apinis, K., Vojdani, V. Efficiently
 *     intertwining widening and narrowing Science of Computer Programming, 120,
 *     pp. 1-24. 2016
 *
 * @tparam V
 *   the type of the values to combine.
 */

abstract class Combo[V] extends ((V, V) => V):
  /**
   * I do not like very much the fact Combo is both a combo and its blueprint.
   * Having two separate classes would be better from the point of view of the
   * separation of concerns, but this solution is quite convenient. We do not
   * write redundant code, we only need a copy method to properly and
   * efficiently handle mutable objects, the API is simple. Keep in mind that
   * one of the important point of this design is to reduce duplication of
   * combos in combo assignments as much as possible.
   */

  /**
   * It returns true if the combo is guaranteed to be idempotent, i.e., if `x
   * combo y = (x combo y) combo y`. This may be used for optimization purposes.
   */
  def isIdempotent: Boolean

  /**
   * It returns true if this is guaranteed to be the right combo (i.e., the one
   * which always returns the second component: `x combo y = y). This may be
   * used for optimization purposes.
   */
  def isRight: Boolean

  /**
   * It returns true if this combo is guaranteed to be immutable, i.e., if the
   * `apply` method does not change its behaviour over time.
   */
  def isImmutable: Boolean

  /**
   * Returns a copy of this combo. An immutable combo may just returns itself,
   * but a mutable one should produce a distinct copy of itself.
   */
  def copy: Combo[V]

  /**
   * Returns a delayed version of the combo. It is equivalent to applying
   * cascade to the right combo.
   */
  def delayed(delay: Int): Combo[V] = Combo.cascade(Combo.right[V], delay, this)

/** The `Combo` object defines several factories for building combos. */
object Combo:

  abstract class ImmutableCombo[V] extends Combo[V]:
    def isImmutable = true
    def copy: this.type = this

  private object RightCombo extends ImmutableCombo[Any]:
    def apply(x: Any, y: Any): Any = y
    def isRight = true
    def isIdempotent = true

  private object LeftCombo extends ImmutableCombo[Any]:
    def apply(x: Any, y: Any): Any = x
    def isRight = false
    def isIdempotent = true

  // We assume f is immutable, since we would not know how to handle the case with f mutable.
  private final class FromFunction[V](f: (V, V) => V, val isIdempotent: Boolean)
      extends ImmutableCombo[V]:
    def apply(x: V, y: V): V = f(x, y)
    def isRight = false

  // we only consider the case when either `first` or `second` is not a right combo
  private final class Warrowing[V: Domain](widening: Combo[V], narrowing: Combo[V])
      extends Combo[V]:
    def apply(x: V, y: V): V =
      if y <= x then narrowing(x, y) else widening(x, y)
    def isRight: Boolean = false
    def isIdempotent: Boolean = false
    def isImmutable: Boolean = widening.isImmutable && narrowing.isImmutable
    def copy: Warrowing[V] = if isImmutable then this else Warrowing(widening.copy, narrowing.copy)

  // we only consider the case when `delay` > 0 and either `first` or `second` is not a right combo
  private final class Cascade[V](first: Combo[V], delay: Int, second: Combo[V]) extends Combo[V]:
    var steps = 0

    def isRight = false
    def isIdempotent = false
    def isImmutable = false
    def copy = Cascade(first.copy, delay, second.copy)

    def apply(x: V, y: V): V =
      if steps < delay then
        steps += 1
        first(x, y)
      else second(x, y)

  /** A combo which always returns its right component (new contribution). */
  def right[V]: ImmutableCombo[V] = RightCombo.asInstanceOf[ImmutableCombo[V]]

  /** A combo which always returns its left component (original value). */
  def left[V]: ImmutableCombo[V] = LeftCombo.asInstanceOf[ImmutableCombo[V]]

  /**
   * A combo built from a function `f: (V,V) => V`. The combo is declared to be
   * immutable, while idempotency depends on the parameter `areIdempotent`
   *
   * @param f
   *   the function to use for the `apply` method of the new combo.
   * @param isIdempotent
   *   determines whether the returned combo is declared to be idempotent
   *   (default is `true`)
   */
  def apply[V](f: (V, V) => V, isIdempotent: Boolean = true): Combo[V] =
    FromFunction(f, isIdempotent)

  /**
   * A combo given by the upper bound of a type `V` endowed with a directed
   * partial ordering.
   */
  def upperBound[V: Domain]: ImmutableCombo[V] = FromFunction(summon[Domain[V]].upperBound, true)

  /**
   * A mutable combo which behaves as `first` for the initial `delay` steps and
   * as `second` for the rest of its existence. This may be used to implement
   * delayed widenings and narrowings.
   */
  def cascade[V](first: Combo[V], delay: Int, second: Combo[V]): Combo[V] =
    require(delay >= 0)
    if first.isRight && second.isRight then Combo.right[V]
    else if delay == 0 then second
    else Cascade(first, delay, second)

  /**
   * A warrowing obtained by combining the given widenings and narrowings.
   *
   * Warrowing is defined in the paper:
   *   - Amato, G., Scozzari, F., Seidl, H., Apinis, K., Vojdani, V. Efficiently
   *     intertwining widening and narrowing Science of Computer Programming,
   *     120, pp. 1-24. 2016
   *
   * @tparam V
   *   the type of values, should be endowed with a partial ordering
   * @param widening
   *   a widening over V
   * @param narrowing
   *   a narrowing over V
   */
  def warrowing[V: Domain](widening: Combo[V], narrowing: Combo[V]): Combo[V] =
    if widening.isRight && narrowing.isRight then right[V]
    else Warrowing(widening, narrowing)




© 2015 - 2025 Weber Informatics LLC | Privacy Policy