Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parse ASSUME declarations names #2808

Merged
merged 5 commits into from
Jan 22, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .unreleased/features/01.md
fan-tom marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Parse name of `ASSUME` declarations into IR and preserve them during serialization to JSON, see #2808
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,10 @@ class JsonToTla[T <: JsonRepresentation](
opDecl

case "TlaAssumeDecl" =>
val definedName = declJson.getFieldOpt("name").map(scalaFactory.asStr)
val bodyField = getOrThrow(declJson, "body")
val body = asTlaEx(bodyField)
TlaAssumeDecl(body)(typeTag)
TlaAssumeDecl(definedName, body)(typeTag)
case _ => throw new JsonDeserializationError(s"$kind is not a valid TlaDecl kind")
}
setLoc(decl, getSourceLocationOpt(declJson))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,10 @@ class JsonToTlaViaBuilder[T <: JsonRepresentation](
opDecl

case "TlaAssumeDecl" =>
val definedName = declJson.getFieldOpt("name").map(scalaFactory.asStr)
val bodyField = getOrThrow(declJson, "body")
val body = asTBuilderInstruction(bodyField)
TlaAssumeDecl(body)(typeTag)
TlaAssumeDecl(definedName, body)(typeTag)
case _ => throw new JsonDeserializationError(s"$kind is not a valid TlaDecl kind")
}
setLoc(decl, getSourceLocationOpt(declJson))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,13 +188,20 @@ class TlaToJson[T <: JsonRepresentation](
"body" -> bodyJson,
)

case TlaAssumeDecl(body) =>
case TlaAssumeDecl(definedName, body) =>
val bodyJson = apply(body)
withLoc(
val fields = Array[(String, T)](
typeFieldName -> typeTagPrinter(decl.typeTag),
kindFieldName -> "TlaAssumeDecl",
"body" -> bodyJson,
)

val f = definedName match {
case None => fields
case Some(name) => fields :+ ("name" -> name: (String, T))
fan-tom marked this conversation as resolved.
Show resolved Hide resolved
}

withLoc(f.toIndexedSeq: _*)
fan-tom marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -556,8 +556,12 @@ class PrettyWriter(
"VARIABLE" <> nest(line <> wrapWithComment(annotations.get) <> line <> parseableName(name))
}

case TlaAssumeDecl(body) =>
val doc = group("ASSUME" <> parens(exToDoc((0, 0), body, nameResolver)))
case TlaAssumeDecl(definedName, body) =>
val doc = definedName match {
case None => group("ASSUME" <> parens(exToDoc((0, 0), body, nameResolver)))
case Some(name) => group("ASSUME" <+> name <+> "==" <+> parens(exToDoc((0, 0), body, nameResolver)))
}

if (annotations.isEmpty) {
doc
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -737,10 +737,11 @@ class Quint(quintOutput: QuintOutput) {
// no methods for them are provided by the ScopedBuilder.
case QuintConst(id, name, _) => Some(None, TlaConstDecl(name)(typeTagOfId(id)))
case QuintVar(id, name, _) => Some(None, TlaVarDecl(name)(typeTagOfId(id)))
case QuintAssume(_, _, quintEx) =>
case d @ QuintAssume(_, name, quintEx) =>
val tlaEx = build(tlaExpression(quintEx).run(nullaryOps))
val definedName = Option.unless(d.isUnnamed)(name)
// assume declarations have no entry in the type map, and are always typed bool
Some(None, TlaAssumeDecl(tlaEx)(Typed(BoolT1)))
Some(None, TlaAssumeDecl(definedName, tlaEx)(Typed(BoolT1)))
case op: QuintOpDef if op.qualifier == "run" =>
// We don't currently support run definitions
None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,16 @@ private[quint] object QuintDef {
name: String,
/** an expression to associate with the name */
assumption: QuintEx)
extends QuintDef {}
extends QuintDef {

/**
* @return
* true if this ASSUME clause has no user-defined name, false otherwise
*
* unnamed ASSUME clauses use `_` as a name
fan-tom marked this conversation as resolved.
Show resolved Hide resolved
*/
def isUnnamed: Boolean = name == "_"
}
object QuintAssume {
implicit val rw: RW[QuintAssume] = macroRW
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class AssumeTranslator(
context,
OutsideRecursion(),
).translate(node.getAssume)
TlaAssumeDecl(body)(Untyped)
TlaAssumeDecl(Option(node.getDef).map(_.getName.toString), body)(Untyped)
fan-tom marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,32 @@ class TestPrettyWriterWithTypes extends AnyFunSuite with BeforeAndAfterEach {
|""".stripMargin
assert(expected == stringWriter.toString)
}

test("unnamed assume declaration") {
val decl = TlaAssumeDecl(None, tla.eql(tla.name("x"), tla.bool(true)))
fan-tom marked this conversation as resolved.
Show resolved Hide resolved
val store = createAnnotationStore()

val writer = new PrettyWriterWithAnnotations(store, printWriter, layout80)
writer.write(decl)
printWriter.flush()
val expected =
"""ASSUME(x = TRUE)
|
|""".stripMargin
assert(expected == stringWriter.toString)
}

test("named assume declaration") {
val decl = TlaAssumeDecl(Some("myAssume"), tla.eql(tla.name("x"), tla.bool(true)))
fan-tom marked this conversation as resolved.
Show resolved Hide resolved
val store = createAnnotationStore()

val writer = new PrettyWriterWithAnnotations(store, printWriter, layout80)
writer.write(decl)
printWriter.flush()
val expected =
"""ASSUME myAssume == (x = TRUE)
|
|""".stripMargin
assert(expected == stringWriter.toString)
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package at.forsyte.apalache.io.json

import at.forsyte.apalache.io.json.impl.TlaToUJson
import at.forsyte.apalache.tla.lir.{TestingPredefs, TlaConstDecl, TlaDecl, TlaEx, TlaVarDecl, TypeTag}
import at.forsyte.apalache.tla.lir.{TestingPredefs, TlaAssumeDecl, TlaConstDecl, TlaDecl, TlaEx, TlaVarDecl, TypeTag}
import at.forsyte.apalache.tla.lir.convenience.tla
import at.forsyte.apalache.tla.lir.oper.{TlaFunOper, TlaSetOper}
import org.junit.runner.RunWith
Expand Down Expand Up @@ -89,15 +89,25 @@ class TestTlaToUJson extends AnyFunSuite with BeforeAndAfterEach with TestingPre
test("Non-operator declarations") {
val constDecl = TlaConstDecl("C")
val varDecl = TlaVarDecl("x")
val namedAssumeDecl = TlaAssumeDecl(Some("myAssume"), tla.eql(tla.name("x"), tla.bool(true)))
val unnamedAssumeDecl = TlaAssumeDecl(None, tla.eql(tla.name("x"), tla.bool(true)))

val constJson = getEncVal(constDecl)
val varJson = getEncVal(varDecl)
val namedAssumeJson = getEncVal(namedAssumeDecl)
val unnamedAssumeJson = getEncVal(unnamedAssumeDecl)

assert(constJson(kindField).str == "TlaConstDecl")
assert(constJson("name").str == constDecl.name)

assert(varJson(kindField).str == "TlaVarDecl")
assert(varJson("name").str == varDecl.name)

assert(namedAssumeJson(kindField).str == "TlaAssumeDecl")
assert(namedAssumeJson("name").str == namedAssumeDecl.name)

assert(unnamedAssumeJson(kindField).str == "TlaAssumeDecl")
assert(!unnamedAssumeJson.obj.contains("name"))
}

test("Operator declarations") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class TestUJsonToTla extends AnyFunSuite with Checkers {

val decls: Seq[TlaDecl] = Seq(
tla.declOp("X", tla.eql(tla.name("a"), tla.int(1)), OperParam("a")),
TlaAssumeDecl(tla.eql(tla.int(1), tla.int(0))),
TlaAssumeDecl(None, tla.eql(tla.int(1), tla.int(0))),
TlaConstDecl("c"),
TlaVarDecl("v"),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ class TestUJsonToTlaViaBuilder extends AnyFunSuite with Checkers {

val decls: Seq[TlaDecl] = Seq(
tla.decl("X", tla.eql(tla.name("a", IntT1), tla.int(1)), tla.param("a", IntT1)),
TlaAssumeDecl(tla.eql(tla.int(1), tla.int(0))),
TlaAssumeDecl(None, tla.eql(tla.int(1), tla.int(0))),
TlaConstDecl("c"),
TlaVarDecl("v"),
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package at.forsyte.apalache.io.quint

import at.forsyte.apalache.tla.lir.{IntT1, OperT1, RecRowT1, RowT1, SetT1, TlaEx, TlaType1, Typed, VarT1}
import at.forsyte.apalache.tla.lir.{
BoolT1, IntT1, OperT1, RecRowT1, RowT1, SetT1, TlaAssumeDecl, TlaEx, TlaType1, Typed, ValEx, VarT1,
}
import org.junit.runner.RunWith
import org.scalatest.funsuite.AnyFunSuite
import org.scalatestplus.junit.JUnitRunner
import QuintType._
import QuintEx._
import at.forsyte.apalache.io.quint.QuintDef.QuintAssume
import at.forsyte.apalache.tla.lir.values.TlaBool
import at.forsyte.apalache.tla.typecomp.build

// You can run all these tests in watch mode in the
Expand Down Expand Up @@ -789,4 +793,18 @@ class TestQuintEx extends AnyFunSuite {
val err = intercept[QuintIRParseError](convert(Q.oneOfSet))
assert(err.getMessage().contains("`oneOf` can only occur as the principle operator of a `nondet` declaration"))
}

test("can convert ASSUME declaration") {
val translator = new Quint(Q.quintOutput)
val nullaryOps = Set[String]()

val name = "myAssume"
val namedAssume = QuintAssume(1, name, QuintBool(2, true))
val tlaNamedAssumeDef = translator.tlaDef(namedAssume).run(nullaryOps).get._2
assert(tlaNamedAssumeDef == TlaAssumeDecl(Some(name), ValEx(TlaBool(true))(Typed(BoolT1)))(Typed(BoolT1)))

val unnamedAssume = QuintAssume(1, "_", QuintBool(2, true))
val tlaUnnamedAssumeDef = translator.tlaDef(unnamedAssume).run(nullaryOps).get._2
assert(tlaUnnamedAssumeDef == TlaAssumeDecl(None, ValEx(TlaBool(true))(Typed(BoolT1)))(Typed(BoolT1)))
fan-tom marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package at.forsyte.apalache.io.quint

import at.forsyte.apalache.io.quint.QuintDef.QuintAssume
import at.forsyte.apalache.io.quint.QuintEx.QuintBool
import org.junit.runner.RunWith
import org.scalatest.funsuite.AnyFunSuite
import org.scalatestplus.junit.JUnitRunner
Expand Down Expand Up @@ -37,6 +39,14 @@ class TestQuintIR extends AnyFunSuite {
assert(QuintDeserializer.write[BigInt](BigInt(s"-${someBigIntStr}")) == s"-${someBigIntStr}")
}

test("QuintAssume#isUnnamed works correctly") {
val namedAssume = QuintAssume(1, "myAssume", QuintBool(2, true))
assert(!namedAssume.isUnnamed)

val unnamedAssume = QuintAssume(1, "_", QuintBool(2, true))
assert(unnamedAssume.isUnnamed)
}

// tictactoe.json is located in tla-io/src/test/resources/tictactoe.json
test("Can load tictactoe.json") {
val tictactoeQuintJson = scala.io.Source.fromResource("tictactoe.json").mkString
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1808,12 +1808,14 @@ class TestSanyImporter extends SanyImporterTestBase {

test("assumptions") {
// checking that the assumptions are imported properly
val assumptionName = "nonZero"
val text =
"""
s"""
|---- MODULE assumptions ----
|CONSTANT N
|ASSUME N = 4
|ASSUME N /= 10
|ASSUME $assumptionName == N /= 0
|================================
|""".stripMargin

Expand All @@ -1825,20 +1827,28 @@ class TestSanyImporter extends SanyImporterTestBase {
expectSourceInfoInDefs(root)

modules(rootName).declarations(1) match {
case TlaAssumeDecl(e) => assert(eql(name("N"), int(4)).untyped() == e)
case TlaAssumeDecl(_, e) => assert(eql(name("N"), int(4)).untyped() == e)

case e @ _ => fail("expected an assumption, found: " + e)
}

modules(rootName).declarations(2) match {
case TlaAssumeDecl(e) => assert(neql(name("N"), int(10)).untyped() == e)
case TlaAssumeDecl(_, e) => assert(neql(name("N"), int(10)).untyped() == e)

case e @ _ => fail("expected an assumption, found: " + e)
}

modules(rootName).declarations(3) match {
case TlaAssumeDecl(definedName, e) =>
assert(neql(name("N"), int(0)).untyped() == e)
assert(definedName contains assumptionName)

case e @ _ => fail("expected an assumption, found: " + e)
}

// regression test for issue #25
val names = HashSet(modules(rootName).assumeDeclarations.map(_.name): _*)
assert(2 == names.size) // all assumptions must have unique names
assert(3 == names.size) // all assumptions must have unique names
}

test("ignore theorems") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,12 @@ class ToEtcExpr(
val operType = OperT1(Seq(BoolT1), BoolT1)
val application = mkUniqApp(Seq(operType), this(d.body))
// We have to introduce a lambda abstraction, as the type checker is expecting this form.
mkLet(BlameRef(d.ID), "__Assume_" + d.ID, mkAbs(ExactRef(d.ID), application), inScopeEx)
mkLet(
BlameRef(d.ID),
"__Assume_" + d.definedName.getOrElse(d.ID.toString),
fan-tom marked this conversation as resolved.
Show resolved Hide resolved
mkAbs(ExactRef(d.ID), application),
inScopeEx,
)

case d: TlaOperDecl =>
// Foo(x) == ...
Expand Down Expand Up @@ -131,7 +136,7 @@ class ToEtcExpr(
OperT1(nBools, BoolT1)
}

// Valid when the input seq has two items, the first of which is a VlaEx(TlaStr(_))
// Valid when the input seq has two items, the first of which is a ValEx(TlaStr(_))
fan-tom marked this conversation as resolved.
Show resolved Hide resolved
private val validateRecordPair: Seq[TlaEx] => (String, TlaEx) = {
// Only pairs coordinating pairs and sets are valid. See TlaSetOper.recSet
case Seq(ValEx(TlaStr(name)), set) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ class TypeRewriter(tracker: TransformationTracker, defaultTag: UID => TypeTag)(t
case d @ TlaVarDecl(_) =>
decl.withTag(getOrDefault(d.ID))

case d @ TlaAssumeDecl(body) =>
TlaAssumeDecl(this(body))(getOrDefault(d.ID))
case d @ TlaAssumeDecl(definedName, body) =>
TlaAssumeDecl(definedName, this(body))(getOrDefault(d.ID))

case d @ TlaTheoremDecl(name, body) =>
TlaTheoremDecl(name, this(body))(getOrDefault(d.ID))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ class TestToEtcExprDecls extends AnyFunSuite with ToEtcExprBase with BeforeAndAf
}

test("assumes") {
val assume = TlaAssumeDecl(tla.name("x"))
val assume = TlaAssumeDecl(None, tla.name("x"))
val terminal = mkUniqConst(BoolT1)
// becomes:
// let Assume1 == ((Bool => Bool) "x") in
Expand All @@ -116,7 +116,8 @@ class TestToEtcExprDecls extends AnyFunSuite with ToEtcExprBase with BeforeAndAf
// The body is wrapped with the application of an operator that has the signature Bool => Bool.
// This allows us to check that the assumption has Boolean type.
val application = mkUniqApp(Seq(parser("Bool => Bool")), assumption)
val expected = mkUniqLet("__Assume_" + assume.ID, mkUniqAbs(application), terminal)
val expected =
mkUniqLet("__Assume_" + assume.definedName.getOrElse(assume.ID.toString), mkUniqAbs(application), terminal)
fan-tom marked this conversation as resolved.
Show resolved Hide resolved
// Translate the declaration of positive.
// We have to pass the next expression in scope, which is just TRUE in this case.
assert(expected == mkToEtcExpr(Map())(assume, terminal))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,16 @@ case class TlaVarDecl(name: String)(implicit typeTag: TypeTag) extends TlaDecl w
/**
* An assumption defined by ASSUME(...)
*
* @param definedName
* optional assumption name, like name in `ASSUME name == x = 4`, or none, like in `ASSUME x = 4`
* @param body
* the assumption body
*/
case class TlaAssumeDecl(body: TlaEx)(implicit typeTag: TypeTag) extends TlaDecl with Serializable {
val name: String = "ASSUME" + body.ID
case class TlaAssumeDecl(definedName: Option[String], body: TlaEx)(implicit typeTag: TypeTag)
extends TlaDecl with Serializable {
override val name: String = definedName.getOrElse("ASSUME" + body.ID)

override def withTag(newTypeTag: TypeTag): TlaAssumeDecl = TlaAssumeDecl(body)(newTypeTag)
override def withTag(newTypeTag: TypeTag): TlaAssumeDecl = TlaAssumeDecl(definedName, body)(newTypeTag)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ class TlaLevelFinder(module: TlaModule) {
case TlaVarDecl(_) =>
TlaLevelState

case TlaAssumeDecl(_) =>
case TlaAssumeDecl(_, _) =>
TlaLevelConst

case TlaOperDecl(name, _, body) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,8 +243,11 @@ object UTFPrinter extends Printer {
case TlaVarDecl(name) =>
"VARIABLE " + name

case TlaAssumeDecl(body) =>
apply(body)
case TlaAssumeDecl(definedName, body) =>
definedName match {
case None => s"ASSUME " + apply(body)
case Some(name) => s"ASSUME $name ${m_defeq} " + apply(body)
}

case TlaOperDecl(name, formalParams, body) =>
val ps =
Expand Down
Loading
Loading