Skip to content

Commit 89cd2f2

Browse files
committed
Allow adt constructors to have associated axiom
1 parent 5295393 commit 89cd2f2

File tree

6 files changed

+72
-23
lines changed

6 files changed

+72
-23
lines changed

src/main/scala/viper/silver/parser/ParseAst.scala

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ object PDomainFunctionArg {
315315
case class PIdnTypeBinding(idndef: PIdnDef, c: PSym.Colon, typ: PType)(val pos: (Position, Position))
316316

317317
/** Anything that can be `PIdnUse`d as an expression (e.g. the receiver of a `PFieldAccess`). */
318-
sealed trait PTypedVarDecl extends PTypedDeclaration with PDeclarationInner {
318+
trait PTypedVarDecl extends PTypedDeclaration with PDeclarationInner {
319319
def idndef: PIdnDef
320320
def toIdnUse: PIdnUseExp = {
321321
val ref = PIdnRef[PTypedVarDecl](idndef.name)(idndef.pos)
@@ -1691,7 +1691,11 @@ case class PDomainFunction(annotations: Seq[PAnnotation], unique: Option[PKw.Uni
16911691
override def endLinebreak = false
16921692
}
16931693

1694-
case class PAxiom(annotations: Seq[PAnnotation], axiom: PKw.Axiom, idndef: Option[PIdnDef], exp: PBracedExp)(val pos: (Position, Position)) extends PDomainMember
1694+
/** Declares that all children expressions must always be well-defined. This is
1695+
* enforced during type-checking. Used e.g. for axioms. */
1696+
trait PAlwaysWellDefined
1697+
1698+
case class PAxiom(annotations: Seq[PAnnotation], axiom: PKw.Axiom, idndef: Option[PIdnDef], exp: PBracedExp)(val pos: (Position, Position)) extends PDomainMember with PAlwaysWellDefined
16951699

16961700
case class PDomainInterpretation(name: PRawString, c: PSym.Colon, lit: PStringLiteral)(val pos: (Position, Position)) extends PNode
16971701
case class PDomainInterpretations(k: PReserved[PKeywordLang], m: PDelimited.Comma[PSym.Paren, PDomainInterpretation])(val pos: (Position, Position)) extends PNode {

src/main/scala/viper/silver/parser/Resolver.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -699,7 +699,7 @@ case class TypeChecker(names: NameAnalyser) {
699699
check(fd.typ)
700700
fd.formalArgs foreach (a => check(a.typ))
701701
}
702-
if (pfa.isDescendant[PAxiom] && pfn.pres.toSeq.exists(pre => pre.k.rs == Requires)) {
702+
if (pfa.isDescendant[PAlwaysWellDefined] && pfn.pres.toSeq.exists(pre => pre.k.rs == Requires)) {
703703
// A domain axiom, which must always be well-defined, is calling a function that has at least
704704
// one real precondition (i.e., not just a requires clause or something similar that's
705705
// temporarily represented as a precondition), which means that the call may not always be

src/main/scala/viper/silver/plugin/standard/adt/AdtASTExtension.scala

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ case class Adt(name: String, constructors: Seq[AdtConstructor], typVars: Seq[Typ
6666
* @param typ the return type of the constructor
6767
* @param adtName the name corresponding of the corresponding ADT
6868
*/
69-
case class AdtConstructor(name: String, formalArgs: Seq[LocalVarDecl])
69+
case class AdtConstructor(name: String, formalArgs: Seq[LocalVarDecl], axiom: Option[Exp])
7070
(val pos: Position, val info: Info, val typ: AdtType, val adtName: String, val errT: ErrorTrafo)
7171
extends ExtensionMember {
7272

@@ -84,19 +84,20 @@ case class AdtConstructor(name: String, formalArgs: Seq[LocalVarDecl])
8484
if (!forceRewrite && this.children == children && pos.isEmpty)
8585
this
8686
else {
87-
assert(children.length == 2, s"AdtConstructor : expected length 2 but got ${children.length}")
87+
assert(children.length == 3, s"AdtConstructor : expected length 3 but got ${children.length}")
8888
val first = children.head.asInstanceOf[String]
8989
val second = children.tail.head.asInstanceOf[Seq[LocalVarDecl]]
90-
AdtConstructor(first, second)(this.pos, this.info, this.typ, this.adtName, this.errT).asInstanceOf[this.type]
90+
val third = children.tail.tail.head.asInstanceOf[Option[Exp]]
91+
AdtConstructor(first, second, third)(this.pos, this.info, this.typ, this.adtName, this.errT).asInstanceOf[this.type]
9192
}
9293

9394
}
9495
}
9596

9697
object AdtConstructor {
97-
def apply(adt: Adt, name: String, formalArgs: Seq[LocalVarDecl])
98+
def apply(adt: Adt, name: String, formalArgs: Seq[LocalVarDecl], axiom: Option[Exp])
9899
(pos: Position = NoPosition, info: Info = NoInfo, errT: ErrorTrafo = NoTrafos): AdtConstructor = {
99-
AdtConstructor(name, formalArgs)(pos, info, AdtType(adt, (adt.typVars zip adt.typVars).toMap), adt.name, errT)
100+
AdtConstructor(name, formalArgs, axiom)(pos, info, AdtType(adt, (adt.typVars zip adt.typVars).toMap), adt.name, errT)
100101
}
101102
}
102103

src/main/scala/viper/silver/plugin/standard/adt/AdtPASTExtension.scala

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,15 +100,15 @@ case class PAdtSeq[T <: PNode](seq: PDelimited.Block[T])(val pos: (Position, Pos
100100
}
101101

102102
/** Any argument to a method, function or predicate. */
103-
case class PAdtFieldDecl(idndef: PIdnDef, c: PSym.Colon, typ: PType)(val pos: (Position, Position)) extends PAnyFormalArgDecl with PTypedDeclaration with PGlobalDeclaration with PMemberUniqueDeclaration with PAdtChild {
103+
case class PAdtFieldDecl(idndef: PIdnDef, c: PSym.Colon, typ: PType)(val pos: (Position, Position)) extends PAnyFormalArgDecl with PTypedVarDecl with PGlobalDeclaration with PMemberUniqueDeclaration with PAdtChild {
104104
def constructor: PAdtConstructor = getAncestor[PAdtConstructor].get
105105
def annotations: Seq[PAnnotation] = Nil
106106
}
107107
object PAdtFieldDecl {
108108
def apply(d: PIdnTypeBinding): PAdtFieldDecl = PAdtFieldDecl(d.idndef, d.c, d.typ)(d.pos)
109109
}
110110

111-
case class PAdtConstructor(annotations: Seq[PAnnotation], idndef: PIdnDef, args: PDelimited.Comma[PSym.Paren, PAdtFieldDecl])(val pos: (Position, Position)) extends PExtender with PNoSpecsFunction with PGlobalUniqueDeclaration with PAdtChild {
111+
case class PAdtConstructor(annotations: Seq[PAnnotation], idndef: PIdnDef, args: PDelimited.Comma[PSym.Paren, PAdtFieldDecl], axiom: Option[(PKw.Axiom, PGrouped.Paren[PExp])])(val pos: (Position, Position)) extends PExtender with PNoSpecsFunction with PGlobalUniqueDeclaration with PAdtChild with PAlwaysWellDefined {
112112
override def resultType: PType = adt.getAdtType
113113
def fieldDecls: Seq[PAdtFieldDecl] = this.args.inner.toSeq
114114
override def typecheck(t: TypeChecker, n: NameAnalyser): Option[Seq[String]] = {
@@ -119,14 +119,18 @@ case class PAdtConstructor(annotations: Seq[PAnnotation], idndef: PIdnDef, args:
119119
val decl = discClashes.head
120120
t.messages ++= FastMessaging.message(idndef, "corresponding adt discriminator identifier `" + decl.idndef.name + "` at " + idndef.pos._1 + " is shadowed at " + decl.idndef.pos._1)
121121
}
122+
axiom foreach (a => {
123+
t.check(a._2.inner, TypeHelper.Bool)
124+
})
122125
None
123126
}
124127

125128
override def translateMemberSignature(t: Translator): AdtConstructor = {
126129
AdtConstructor(
127130
PAdt.findAdt(adt.idndef, t),
128131
idndef.name,
129-
formalArgs map (_.asInstanceOf[PAdtFieldDecl]) map { arg => LocalVarDecl(arg.idndef.name, t.ttyp(arg.typ))(t.liftPos(arg.idndef)) }
132+
formalArgs map (_.asInstanceOf[PAdtFieldDecl]) map { arg => LocalVarDecl(arg.idndef.name, t.ttyp(arg.typ))(t.liftPos(arg.idndef)) },
133+
axiom map (axiom => t.exp(axiom._2.inner))
130134
)(t.liftPos(this), Translator.toInfo(annotations, this))
131135
}
132136

src/main/scala/viper/silver/plugin/standard/adt/AdtPlugin.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class AdtPlugin(@unused reporter: viper.silver.reporter.Reporter,
2020
config: viper.silver.frontend.SilFrontendConfig,
2121
fp: FastParser) extends SilverPlugin with ParserPluginTemplate {
2222

23-
import fp.{annotation, argList, idnTypeBinding, idndef, idnref, semiSeparated, typ, typeList, domainTypeVarDecl, ParserExtension, lineCol, _file}
23+
import fp.{annotation, argList, idnTypeBinding, idndef, idnref, parenthesizedExp, semiSeparated, typ, typeList, domainTypeVarDecl, ParserExtension, lineCol, _file}
2424
import FastParserCompanion.{ExtendedParsing, LeadingWhitespace, PositionParsing, reservedKw, reservedSym, whitespace}
2525

2626
/**
@@ -91,7 +91,9 @@ class AdtPlugin(@unused reporter: viper.silver.reporter.Reporter,
9191

9292
def adtConstructorDecls[$: P]: P[PAdtSeq[PAdtConstructor]] = P(semiSeparated(adtConstructorDecl).braces.map(PAdtSeq.apply _)).pos
9393

94-
def adtConstructorDecl[$: P]: P[PAdtConstructor] = P((annotation.rep ~ idndef ~ argList(idnTypeBinding.map(PAdtFieldDecl(_)))) map (PAdtConstructor.apply _).tupled).pos
94+
def adtConstructorDecl[$: P]: P[PAdtConstructor] = P((annotation.rep ~ idndef ~ argList(idnTypeBinding.map(PAdtFieldDecl(_))) ~ adtConstructorAxiom.?) map (PAdtConstructor.apply _).tupled).pos
95+
96+
def adtConstructorAxiom[$: P]: P[(PKw.Axiom, PGrouped.Paren[PExp])] = P((P(PKw.Axiom) ~ parenthesizedExp))
9597

9698
override def beforeResolve(input: PProgram): PProgram = {
9799
if (deactivated) {

src/main/scala/viper/silver/plugin/standard/adt/encoding/AdtEncoder.scala

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ class AdtEncoder(val program: Program) extends AdtNameManager {
9090
val domain = Domain(name, null, null, typVars)(adt.pos, adt.info, adt.errT)
9191
val functions: Seq[DomainFunc] = (constructors map encodeAdtConstructorAsDomainFunc(domain)) ++
9292
(constructors flatMap generateDestructorDeclarations(domain)) ++ Seq(generateTagDeclaration(domain))
93-
val axioms = (constructors flatMap generateInjectivityAxiom(domain)) ++
93+
val axioms = (constructors flatMap generateInjectivityAxiom(domain)) ++ (constructors flatMap generatePreconditionAxiom(domain)) ++
9494
(constructors map generateTagAxiom(domain)) ++ Seq(generateExclusivityAxiom(domain)(constructors))
9595
val derivingAxioms = if (derivingInfo.contains(getContainsFunctionName))
9696
constructors filter (_.formalArgs.nonEmpty) map generateContainsAxiom(domain, derivingInfo(getContainsFunctionName)._2) else Seq.empty
@@ -136,6 +136,11 @@ class AdtEncoder(val program: Program) extends AdtNameManager {
136136
}
137137
}
138138

139+
private def formalArgsToLocalVars(ac: AdtConstructor): Seq[LocalVar] = ac.formalArgs map (lv => lv.typ match {
140+
case a: AdtType => localVarTFromType(encodeAdtTypeAsDomainType(a), Some(lv.name))(ac.pos, ac.info, ac.errT)
141+
case d => localVarTFromType(d, Some(lv.name))(ac.pos, ac.info, ac.errT)
142+
})
143+
139144
/**
140145
* This method encodes an ADT constructor as a domain function
141146
*
@@ -145,7 +150,7 @@ class AdtEncoder(val program: Program) extends AdtNameManager {
145150
*/
146151
private def encodeAdtConstructorAsDomainFunc(domain: Domain)(ac: AdtConstructor): DomainFunc = {
147152
ac match {
148-
case AdtConstructor(name, formalArgs) =>
153+
case AdtConstructor(name, formalArgs, _) =>
149154
DomainFunc(name, formalArgs, encodeAdtTypeAsDomainType(ac.typ))(ac.pos, ac.info, domain.name, ac.errT)
150155
}
151156
}
@@ -222,7 +227,7 @@ class AdtEncoder(val program: Program) extends AdtNameManager {
222227
* This method generates the corresponding injectivity axiom for an ADT constructor.
223228
*
224229
* axiom {
225-
* forall p_1: T_1, ..., p_n: T_n :: {C(p_1, ..., p_n)} p_i == D_Ci(C(p_1, ..., p_n))
230+
* forall p_1: T_1, ..., p_n: T_n :: {C(p_1, ..., p_n)} p_i == D_i(C(p_1, ..., p_n))
226231
* }
227232
*
228233
* where C is the ADT constructor, D_i the destructor for i-th argument of C
@@ -234,12 +239,7 @@ class AdtEncoder(val program: Program) extends AdtNameManager {
234239
private def generateInjectivityAxiom(domain: Domain)(ac: AdtConstructor): Seq[AnonymousDomainAxiom] = {
235240
assert(domain.name == ac.adtName, "AdtEncoder: An error in the ADT encoding occurred.")
236241
val localVarDecl = ac.formalArgs.collect { case l: LocalVarDecl => l }
237-
val localVars = ac.formalArgs.map { lv =>
238-
lv.typ match {
239-
case a: AdtType => localVarTFromType(encodeAdtTypeAsDomainType(a), Some(lv.name))(ac.pos, ac.info, ac.errT)
240-
case d => localVarTFromType(d, Some(lv.name))(ac.pos, ac.info, ac.errT)
241-
}
242-
}
242+
val localVars = formalArgsToLocalVars(ac)
243243
assert(localVarDecl.size == localVars.size, "AdtEncoder: An error in the ADT encoding occurred.")
244244

245245
val constructorApp = DomainFuncApp(
@@ -256,11 +256,49 @@ class AdtEncoder(val program: Program) extends AdtNameManager {
256256
defaultTypeVarsFromDomain(domain)
257257
)(ac.pos, ac.info, lv.typ, ac.adtName, ac.errT)
258258
val eq = EqCmp(lv, right)(ac.pos, ac.info, ac.errT)
259-
val forall = Forall(localVarDecl, Seq(trigger), eq)(ac.pos, ac.info, ac.errT)
259+
val cond_eq = ac.axiom.map(p => Implies(p, eq)(ac.pos, ac.info, ac.errT)).getOrElse(eq)
260+
val forall = Forall(localVarDecl, Seq(trigger), cond_eq)(ac.pos, ac.info, ac.errT)
260261
AnonymousDomainAxiom(forall)(ac.pos, ac.info, ac.adtName, ac.errT)
261262
}
262263
}
263264

265+
/**
266+
* This method generates the corresponding precondition axiom for an ADT constructor.
267+
*
268+
* axiom {
269+
* forall t: AdtType :: {D_1(t)}...{D_n(t)} pre
270+
* }
271+
*
272+
* where D_i the destructor for i-th argument of C
273+
*
274+
* @param domain The domain the encoded constructor belongs to
275+
* @param ac The adt constructor for which we want to generate the precondition axiom
276+
* @return The precondition axiom if the constructor has a precondition
277+
*/
278+
private def generatePreconditionAxiom(domain: Domain)(ac: AdtConstructor): Option[AnonymousDomainAxiom] = {
279+
assert(domain.name == ac.adtName, "AdtEncoder: An error in the ADT encoding occurred.")
280+
ac.axiom.map(pre => {
281+
val localVarDecl = localVarTDeclFromType(domainTypeFromDomain(domain))(domain.pos, domain.info, domain.errT)
282+
val localVar = localVarTFromType(domainTypeFromDomain(domain))(domain.pos, domain.info, domain.errT)
283+
284+
val destructorCalls = ac.formalArgs.map { lv =>
285+
DomainFuncApp(
286+
getDestructorName(domain.name, lv.name),
287+
Seq(localVar),
288+
defaultTypeVarsFromDomain(domain)
289+
)(domain.pos, domain.info, lv.typ, domain.name, domain.errT)
290+
}
291+
val localVars = formalArgsToLocalVars(ac)
292+
assert(destructorCalls.size == localVars.size, "AdtEncoder: An error in the ADT encoding occurred.")
293+
294+
val triggers = destructorCalls.map { t => Trigger(Seq(t))(domain.pos, domain.info, domain.errT) }
295+
val pre_sub = pre.replace(localVars.zip(destructorCalls).toMap)
296+
AnonymousDomainAxiom(
297+
Forall(Seq(localVarDecl), triggers, pre_sub)(domain.pos, domain.info, domain.errT)
298+
)(domain.pos, domain.info, domain.name, domain.errT)
299+
})
300+
}
301+
264302
/**
265303
* This method generates the corresponding exclusivity axioms for a sequence of constructors.
266304
*

0 commit comments

Comments
 (0)