6
votes

I am fairly new to ScalaCheck (and Scala entirely) so this may be a fairly simple solution

I am using ScalaCheck to generate tests for an AST and verifying that the writer/parser work. I have these files

AST.scala

package com.test

object Operator extends Enumeration {
  val Add, Subtract, Multiply, Divide = Value
}

sealed trait AST
case class Operation(left: AST, op: Operator.Value, right: AST) extends AST
case class Literal(value: Int) extends AST

GenOperation.scala

import com.test.{AST, Literal}

import org.scalacheck._
import Shrink._
import Prop._
import Arbitrary.arbitrary    

object GenLiteral extends Properties("AST::Literal") {
  property("Verify parse/write") = forAll(genLiteral){ (node) =>
    //  val string_version = node.writeToString() // AST -> String
    //  val result = Parse(string_version) // String -> AST
    true
  }

  def genLiteral: Gen[Literal] = for {
    value <- arbitrary[Int]
  } yield Literal(value)

  implicit def shrinkLiteral: Shrink[AST] = Shrink {
    case Literal(value) =>
      for {
        reduced <- shrink(value)
      } yield Literal(reduced)
  }
}

GenOperation.scala

import com.test.{AST, Operation}

import org.scalacheck._
import Gen._
import Shrink._
import Prop._

import GenLiteral._

object GenOperation extends Properties("AST::Operation") {
  property("Verify parse/write") = forAll(genOperation){ (node) =>
    //  val string_version = node.writeToString() // AST -> String
    //  val result = Parse(string_version) // String -> AST
    true
  }

  def genOperation: Gen[Operation] = for {
    left <- oneOf(genOperation, genLiteral)
    right <- oneOf(genOperation, genLiteral)
    op <- oneOf(Operator.values.toSeq)
  } yield Operation(left,op,right)

  implicit def shrinkOperation: Shrink[AST] = Shrink {
    case Operation(l,o,r) =>
      (
        for {
          ls <- shrink(l)
          rs <- shrink(r)
        } yield Operation(ls, o, rs)
        ) append (
        for {
          ls <- shrink(l)
        } yield Operation(ls, o, r)
        ) append (
        for {
          rs <- shrink(r)
        } yield Operation(l, o, rs)
        ) append shrink(l) append shrink(r)
  }

}

In the example code I wrote (what is pasted above) I get the error

ambiguous implicit values:
 both method shrinkLiteral in object GenLiteral of type => org.scalacheck.Shrink[com.test.AST]
 and method shrinkOperation in object GenOperation of type => org.scalacheck.Shrink[com.test.AST]
 match expected type org.scalacheck.Shrink[com.test.AST]
          ls <- shrink(l)

How do I write the shrink methods for this?

1

1 Answers

4
votes

You have two implicit instances of Shrink[AST] and so the compiler complains about ambiguous implicit values.

You could re-write your code as:

implicit def shrinkLiteral: Shrink[Literal] = Shrink {
  case Literal(value) => shrink(value).map(Literal)
}

implicit def shrinkOperation: Shrink[Operation] = Shrink {
  case Operation(l,o,r) =>
    shrink(l).map(Operation(_, o, r)) append
    shrink(r).map(Operation(l, o, _)) append ???
}

implicit def shrinkAST: Shrink[AST] = Shrink {
  case o: Operation => shrink(o)
  case l: Literal => shrink(l)
}