From fc657062c9c07d37ff11353ad0951260c4d1e0fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Mon, 15 Aug 2022 18:09:29 -0700 Subject: [PATCH 001/105] git: mv Translator.dfy Translator.Common.dfy --- src/AST/{Translator.dfy => Translator.Common.dfy} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/AST/{Translator.dfy => Translator.Common.dfy} (100%) diff --git a/src/AST/Translator.dfy b/src/AST/Translator.Common.dfy similarity index 100% rename from src/AST/Translator.dfy rename to src/AST/Translator.Common.dfy From a9b8fe7d71b8c8cec1da52521f610411bc093da6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Mon, 15 Aug 2022 18:09:29 -0700 Subject: [PATCH 002/105] git: checkout Translator.dfy --- src/AST/Translator.dfy | 579 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 579 insertions(+) create mode 100644 src/AST/Translator.dfy diff --git a/src/AST/Translator.dfy b/src/AST/Translator.dfy new file mode 100644 index 00000000..c01b50e4 --- /dev/null +++ b/src/AST/Translator.dfy @@ -0,0 +1,579 @@ +include "../Interop/CSharpDafnyASTModel.dfy" +include "../Interop/CSharpInterop.dfy" +include "../Interop/CSharpDafnyInterop.dfy" +include "../Interop/CSharpDafnyASTInterop.dfy" +include "../Utils/Library.dfy" +include "Syntax.dfy" +include "Predicates.dfy" + +module Bootstrap.AST.Translator { + import opened Utils.Lib + import opened Utils.Lib.Datatypes + import opened Interop.CSharpInterop + import opened Interop.CSharpInterop.System + import opened Interop.CSharpDafnyInterop + import opened Interop.CSharpDafnyInterop.Microsoft + import opened Interop.CSharpDafnyASTInterop + import C = Interop.CSharpDafnyASTModel + import D = Syntax + import DE = Syntax.Exprs + import DT = Syntax.Types + import P = Predicates.Deep + + datatype TranslationError = + | Invalid(msg: string) + | GhostExpr(expr: C.Expression) + | UnsupportedType(ty: C.Type) + | UnsupportedExpr(expr: C.Expression) + | UnsupportedStmt(stmt: C.Statement) + | UnsupportedMember(decl: C.MemberDecl) + { + function method ToString() : string { + match this + case Invalid(msg) => + "Invalid term: " + msg + case GhostExpr(expr) => + "Ghost expression: " + TypeConv.ObjectToString(expr) + case UnsupportedType(ty) => + "Unsupported type: " + TypeConv.ObjectToString(ty) + case UnsupportedExpr(expr) => + "Unsupported expression: " + TypeConv.ObjectToString(expr) + case UnsupportedStmt(stmt) => + "Unsupported statement: " + TypeConv.ObjectToString(stmt) + case UnsupportedMember(decl) => + "Unsupported declaration: " + TypeConv.ObjectToString(decl) + } + } + + type Expr = e: DE.Expr | P.All_Expr(e, DE.WellFormed) + witness DE.Block([]) + + type TranslationResult<+A> = + Result + + function method TranslateType(ty: C.Type) + : TranslationResult + reads * + decreases TypeHeight(ty) + { + var ty := TypeUtils.NormalizeExpand(ty); + if ty is C.BoolType then + Success(DT.Bool) + else if ty is C.CharType then + Success(DT.Char) + else if ty is C.IntType then + Success(DT.Int) + else if ty is C.RealType then + Success(DT.Real) + else if ty is C.BigOrdinalType then + Success(DT.BigOrdinal) + else if ty is C.BitvectorType then + var bvTy := ty as C.BitvectorType; + :- Need(bvTy.Width >= 0, Invalid("BV width must be >= 0")); + Success(DT.BitVector(bvTy.Width as int)) + // TODO: the following could be simplified + else if ty is C.MapType then + var ty := ty as C.MapType; + assume TypeHeight(ty.Domain) < TypeHeight(ty); + assume TypeHeight(ty.Range) < TypeHeight(ty); + var domainTy :- TranslateType(ty.Domain); + var rangeTy :- TranslateType(ty.Range); + Success(DT.Collection(ty.Finite, DT.CollectionKind.Map(domainTy), rangeTy)) + else if ty is C.SetType then + var ty := ty as C.SetType; + assume TypeHeight(ty.Arg) < TypeHeight(ty); + var eltTy :- TranslateType(ty.Arg); + Success(DT.Collection(ty.Finite, DT.CollectionKind.Set, eltTy)) + else if ty is C.MultiSetType then + var ty := ty as C.MultiSetType; + assume TypeHeight(ty.Arg) < TypeHeight(ty); + var eltTy :- TranslateType(ty.Arg); + Success(DT.Collection(true, DT.CollectionKind.Multiset, eltTy)) + else if ty is C.SeqType then + var ty := ty as C.SeqType; + assume TypeHeight(ty.Arg) < TypeHeight(ty); + var eltTy :- TranslateType(ty.Arg); + Success(DT.Collection(true, DT.CollectionKind.Seq, eltTy)) + else + Failure(UnsupportedType(ty)) + } + + const GhostUnaryOps: set := + {C.UnaryOpExpr__ResolvedOpcode.Fresh, + C.UnaryOpExpr__ResolvedOpcode.Allocated, + C.UnaryOpExpr__ResolvedOpcode.Lit} + + const UnaryOpMap: map := + map[C.UnaryOpExpr__ResolvedOpcode.BVNot := D.UnaryOps.BVNot, + C.UnaryOpExpr__ResolvedOpcode.BoolNot := D.UnaryOps.BoolNot, + C.UnaryOpExpr__ResolvedOpcode.SeqLength := D.UnaryOps.SeqLength, + C.UnaryOpExpr__ResolvedOpcode.SetCard := D.UnaryOps.SetCard, + C.UnaryOpExpr__ResolvedOpcode.MultiSetCard := D.UnaryOps.MultisetCard, + C.UnaryOpExpr__ResolvedOpcode.MapCard := D.UnaryOps.MapCard] + + const LazyBinopMap: map := + map[C.BinaryExpr__ResolvedOpcode.Imp := DE.Imp, + C.BinaryExpr__ResolvedOpcode.And := DE.And, + C.BinaryExpr__ResolvedOpcode.Or := DE.Or] + + const EagerBinopMap: map := + map[C.BinaryExpr__ResolvedOpcode.Iff := D.BinaryOps.Logical(D.BinaryOps.Iff), + C.BinaryExpr__ResolvedOpcode.EqCommon := D.BinaryOps.Eq(D.BinaryOps.EqCommon), + C.BinaryExpr__ResolvedOpcode.NeqCommon := D.BinaryOps.Eq(D.BinaryOps.NeqCommon), + C.BinaryExpr__ResolvedOpcode.Lt := D.BinaryOps.Numeric(D.BinaryOps.Lt), + C.BinaryExpr__ResolvedOpcode.Le := D.BinaryOps.Numeric(D.BinaryOps.Le), + C.BinaryExpr__ResolvedOpcode.Ge := D.BinaryOps.Numeric(D.BinaryOps.Ge), + C.BinaryExpr__ResolvedOpcode.Gt := D.BinaryOps.Numeric(D.BinaryOps.Gt), + C.BinaryExpr__ResolvedOpcode.Add := D.BinaryOps.Numeric(D.BinaryOps.Add), + C.BinaryExpr__ResolvedOpcode.Sub := D.BinaryOps.Numeric(D.BinaryOps.Sub), + C.BinaryExpr__ResolvedOpcode.Mul := D.BinaryOps.Numeric(D.BinaryOps.Mul), + C.BinaryExpr__ResolvedOpcode.Div := D.BinaryOps.Numeric(D.BinaryOps.Div), + C.BinaryExpr__ResolvedOpcode.Mod := D.BinaryOps.Numeric(D.BinaryOps.Mod), + C.BinaryExpr__ResolvedOpcode.LeftShift := D.BinaryOps.BV(D.BinaryOps.LeftShift), + C.BinaryExpr__ResolvedOpcode.RightShift := D.BinaryOps.BV(D.BinaryOps.RightShift), + C.BinaryExpr__ResolvedOpcode.BitwiseAnd := D.BinaryOps.BV(D.BinaryOps.BitwiseAnd), + C.BinaryExpr__ResolvedOpcode.BitwiseOr := D.BinaryOps.BV(D.BinaryOps.BitwiseOr), + C.BinaryExpr__ResolvedOpcode.BitwiseXor := D.BinaryOps.BV(D.BinaryOps.BitwiseXor), + C.BinaryExpr__ResolvedOpcode.LtChar := D.BinaryOps.Char(D.BinaryOps.LtChar), + C.BinaryExpr__ResolvedOpcode.LeChar := D.BinaryOps.Char(D.BinaryOps.LeChar), + C.BinaryExpr__ResolvedOpcode.GeChar := D.BinaryOps.Char(D.BinaryOps.GeChar), + C.BinaryExpr__ResolvedOpcode.GtChar := D.BinaryOps.Char(D.BinaryOps.GtChar), + C.BinaryExpr__ResolvedOpcode.SeqEq := D.BinaryOps.Sequences(D.BinaryOps.SeqEq), + C.BinaryExpr__ResolvedOpcode.SeqNeq := D.BinaryOps.Sequences(D.BinaryOps.SeqNeq), + C.BinaryExpr__ResolvedOpcode.ProperPrefix := D.BinaryOps.Sequences(D.BinaryOps.ProperPrefix), + C.BinaryExpr__ResolvedOpcode.Prefix := D.BinaryOps.Sequences(D.BinaryOps.Prefix), + C.BinaryExpr__ResolvedOpcode.Concat := D.BinaryOps.Sequences(D.BinaryOps.Concat), + C.BinaryExpr__ResolvedOpcode.InSeq := D.BinaryOps.Sequences(D.BinaryOps.InSeq), + C.BinaryExpr__ResolvedOpcode.NotInSeq := D.BinaryOps.Sequences(D.BinaryOps.NotInSeq), + C.BinaryExpr__ResolvedOpcode.SetEq := D.BinaryOps.Sets(D.BinaryOps.SetEq), + C.BinaryExpr__ResolvedOpcode.SetNeq := D.BinaryOps.Sets(D.BinaryOps.SetNeq), + C.BinaryExpr__ResolvedOpcode.ProperSubset := D.BinaryOps.Sets(D.BinaryOps.ProperSubset), + C.BinaryExpr__ResolvedOpcode.Subset := D.BinaryOps.Sets(D.BinaryOps.Subset), + C.BinaryExpr__ResolvedOpcode.Superset := D.BinaryOps.Sets(D.BinaryOps.Superset), + C.BinaryExpr__ResolvedOpcode.ProperSuperset := D.BinaryOps.Sets(D.BinaryOps.ProperSuperset), + C.BinaryExpr__ResolvedOpcode.Disjoint := D.BinaryOps.Sets(D.BinaryOps.Disjoint), + C.BinaryExpr__ResolvedOpcode.InSet := D.BinaryOps.Sets(D.BinaryOps.InSet), + C.BinaryExpr__ResolvedOpcode.NotInSet := D.BinaryOps.Sets(D.BinaryOps.NotInSet), + C.BinaryExpr__ResolvedOpcode.Union := D.BinaryOps.Sets(D.BinaryOps.Union), + C.BinaryExpr__ResolvedOpcode.Intersection := D.BinaryOps.Sets(D.BinaryOps.Intersection), + C.BinaryExpr__ResolvedOpcode.SetDifference := D.BinaryOps.Sets(D.BinaryOps.SetDifference), + C.BinaryExpr__ResolvedOpcode.MultiSetEq := D.BinaryOps.Multisets(D.BinaryOps.MultisetEq), + C.BinaryExpr__ResolvedOpcode.MultiSetNeq := D.BinaryOps.Multisets(D.BinaryOps.MultisetNeq), + C.BinaryExpr__ResolvedOpcode.MultiSubset := D.BinaryOps.Multisets(D.BinaryOps.MultiSubset), + C.BinaryExpr__ResolvedOpcode.MultiSuperset := D.BinaryOps.Multisets(D.BinaryOps.MultiSuperset), + C.BinaryExpr__ResolvedOpcode.ProperMultiSubset := D.BinaryOps.Multisets(D.BinaryOps.ProperMultiSubset), + C.BinaryExpr__ResolvedOpcode.ProperMultiSuperset := D.BinaryOps.Multisets(D.BinaryOps.ProperMultiSuperset), + C.BinaryExpr__ResolvedOpcode.MultiSetDisjoint := D.BinaryOps.Multisets(D.BinaryOps.MultisetDisjoint), + C.BinaryExpr__ResolvedOpcode.InMultiSet := D.BinaryOps.Multisets(D.BinaryOps.InMultiset), + C.BinaryExpr__ResolvedOpcode.NotInMultiSet := D.BinaryOps.Multisets(D.BinaryOps.NotInMultiset), + C.BinaryExpr__ResolvedOpcode.MultiSetUnion := D.BinaryOps.Multisets(D.BinaryOps.MultisetUnion), + C.BinaryExpr__ResolvedOpcode.MultiSetIntersection := D.BinaryOps.Multisets(D.BinaryOps.MultisetIntersection), + C.BinaryExpr__ResolvedOpcode.MultiSetDifference := D.BinaryOps.Multisets(D.BinaryOps.MultisetDifference), + C.BinaryExpr__ResolvedOpcode.MapEq := D.BinaryOps.Maps(D.BinaryOps.MapEq), + C.BinaryExpr__ResolvedOpcode.MapNeq := D.BinaryOps.Maps(D.BinaryOps.MapNeq), + C.BinaryExpr__ResolvedOpcode.InMap := D.BinaryOps.Maps(D.BinaryOps.InMap), + C.BinaryExpr__ResolvedOpcode.NotInMap := D.BinaryOps.Maps(D.BinaryOps.NotInMap), + C.BinaryExpr__ResolvedOpcode.MapMerge := D.BinaryOps.Maps(D.BinaryOps.MapMerge), + C.BinaryExpr__ResolvedOpcode.MapSubtraction := D.BinaryOps.Maps(D.BinaryOps.MapSubtraction), + C.BinaryExpr__ResolvedOpcode.RankLt := D.BinaryOps.Datatypes(D.BinaryOps.RankLt), + C.BinaryExpr__ResolvedOpcode.RankGt := D.BinaryOps.Datatypes(D.BinaryOps.RankGt)]; + + const BinaryOpCodeMap: map := + (map k | k in LazyBinopMap :: DE.Lazy(LazyBinopMap[k])) + + (map k | k in EagerBinopMap :: DE.Eager(DE.BinaryOp(EagerBinopMap[k]))) + + function method TranslateIdentifierExpr(ie: C.IdentifierExpr) + : (e: TranslationResult) + reads * + { + Success(DE.Var(TypeConv.AsString(ie.Name))) + } + + predicate Decreases(u: object, v: object) + requires u is C.Expression || u is C.Statement + requires v is C.Expression || v is C.Statement + { + ASTHeight(u) < ASTHeight(v) + } + + function method TranslateUnary(u: C.UnaryExpr) + : (e: TranslationResult) + decreases ASTHeight(u), 0 + reads * + { + :- Need(u is C.UnaryOpExpr, UnsupportedExpr(u)); + var u := u as C.UnaryOpExpr; + var op, e := u.ResolvedOp, u.E; + assume Decreases(e, u); + :- Need(op !in GhostUnaryOps, GhostExpr(u)); + :- Need(op in UnaryOpMap.Keys, UnsupportedExpr(u)); + var te :- TranslateExpression(e); + Success(DE.Apply(DE.Eager(DE.UnaryOp(UnaryOpMap[op])), [te])) + } + + function method TranslateBinary(b: C.BinaryExpr) + : (e: TranslationResult) + decreases ASTHeight(b), 0 + reads * + { + var op, e0, e1 := b.ResolvedOp, b.E0, b.E1; + // LATER b.AccumulatesForTailRecursion + assume Decreases(e0, b); + assume Decreases(e1, b); + :- Need(op in BinaryOpCodeMap, UnsupportedExpr(b)); + var t0 :- TranslateExpression(e0); + var t1 :- TranslateExpression(e1); + Success(DE.Apply(BinaryOpCodeMap[op], [t0, t1])) + } + + function method TranslateLiteral(l: C.LiteralExpr) + : (e: TranslationResult) + reads * + { + if l.Value is Boolean then + Success(DE.Literal(DE.LitBool(TypeConv.AsBool(l.Value)))) + else if l.Value is Numerics.BigInteger then + Success(DE.Literal(DE.LitInt(TypeConv.AsInt(l.Value)))) + else if l.Value is BaseTypes.BigDec then + Success(DE.Literal(DE.LitReal(TypeConv.AsReal(l.Value)))) // TODO test + else if l.Value is String then + var str := TypeConv.AsString(l.Value); + if l is C.CharLiteralExpr then + :- Need(|str| == 1, Invalid("CharLiteralExpr must contain a single character.")); + Success(DE.Literal(DE.LitChar(str[0]))) + else if l is C.StringLiteralExpr then + var sl := l as C.StringLiteralExpr; + Success(DE.Literal(DE.LitString(str, sl.IsVerbatim))) + else + Failure(Invalid("LiteralExpr with .Value of type string must be a char or a string.")) + else + Failure(UnsupportedExpr(l)) + } + + function method TranslateApplyExpr(ae: C.ApplyExpr) + : (e: TranslationResult) + reads * + decreases ASTHeight(ae), 0 + { + assume Decreases(ae.Function, ae); + var fn :- TranslateExpression(ae.Function); + var args := ListUtils.ToSeq(ae.Args); + var args :- Seq.MapResult(args, e requires e in args reads * => + assume Decreases(e, ae); TranslateExpression(e)); + Success(DE.Apply(DE.Eager(DE.FunctionCall()), [fn] + args)) + } + + function method TranslateMemberSelect(obj: C.Expression, fullName: System.String) + : (e: TranslationResult) + reads * + decreases ASTHeight(obj), 3 + { + var fname := TypeConv.AsString(fullName); + if obj.Resolved is C.StaticReceiverExpr then + Success(DE.Var(fname)) + else + var obj :- TranslateExpression(obj); + Success(DE.Apply(DE.Eager(DE.UnaryOp(DE.UnaryOps.MemberSelect(fname))), [obj])) + } + + function method TranslateMemberSelectExpr(me: C.MemberSelectExpr) + : (e: TranslationResult) + reads * + decreases ASTHeight(me), 0 + { + assume Decreases(me.Obj, me); + TranslateMemberSelect(me.Obj, me.Member.FullName) + } + + function method TranslateFunctionCallExpr(fce: C.FunctionCallExpr) + : (e: TranslationResult) + reads * + decreases ASTHeight(fce), 0 + { + assume Decreases(fce.Receiver, fce); + var fn :- TranslateMemberSelect(fce.Receiver, fce.Function.FullName); + var args := ListUtils.ToSeq(fce.Args); + var args :- Seq.MapResult(args, e requires e in args reads * => + assume Decreases(e, fce); TranslateExpression(e)); + Success(DE.Apply(DE.Eager(DE.FunctionCall()), [fn] + args)) + } + + function method TranslateDatatypeValue(dtv: C.DatatypeValue) + : (e: TranslationResult) + reads * + decreases ASTHeight(dtv), 0 + { + var ctor := dtv.Ctor; + var n := TypeConv.AsString(ctor.Name); + var typeArgs :- Seq.MapResult(ListUtils.ToSeq(dtv.InferredTypeArgs), TranslateType); + // TODO: also include formals in the following, and filter out ghost arguments + var args := ListUtils.ToSeq(dtv.Arguments); + var args :- Seq.MapResult(args, e requires e in args reads * => + assume Decreases(e, dtv); TranslateExpression(e)); + Success(DE.Apply(DE.Eager(DE.DataConstructor([n], typeArgs)), args)) // TODO: proper path + } + + function method TranslateDisplayExpr(de: C.DisplayExpression) + : (e: TranslationResult) + reads * + decreases ASTHeight(de), 0 + { + var ty :- TranslateType(de.Type); + :- Need(ty.Collection? && ty.finite, Invalid("`DisplayExpr` must be a finite collection.")); + var elems := ListUtils.ToSeq(de.Elements); + var elems :- Seq.MapResult(elems, e requires e in elems reads * => + assume Decreases(e, de); TranslateExpression(e)); + Success(DE.Apply(DE.Eager(DE.Builtin(DE.Display(ty))), elems)) + } + + function method TranslateExpressionPair(mde: C.MapDisplayExpr, ep: C.ExpressionPair) + : (e: TranslationResult) + reads * + requires Math.Max(ASTHeight(ep.A), ASTHeight(ep.B)) < ASTHeight(mde) + decreases ASTHeight(mde), 0 + { + var tyA :- TranslateType(ep.A.Type); + // TODO: This isn't really a sequence of type tyA! It should really construct pairs + var ty := DT.Collection(true, DT.CollectionKind.Seq, tyA); + var tA :- TranslateExpression(ep.A); + var tB :- TranslateExpression(ep.B); + Success(DE.Apply(DE.Eager(DE.Builtin(DE.Display(ty))), [tA, tB])) + } + + function method TranslateMapDisplayExpr(mde: C.MapDisplayExpr) + : (e: TranslationResult) + reads * + decreases ASTHeight(mde), 1 + { + var ty :- TranslateType(mde.Type); + :- Need(ty.Collection? && ty.kind.Map? && ty.finite, Invalid("`MapDisplayExpr` must be a map.")); + var elems := ListUtils.ToSeq(mde.Elements); + var elems :- Seq.MapResult(elems, (ep: C.ExpressionPair) requires ep in elems reads * => + assume Math.Max(ASTHeight(ep.A), ASTHeight(ep.B)) < ASTHeight(mde); + TranslateExpressionPair(mde, ep)); + Success(DE.Apply(DE.Eager(DE.Builtin(DE.Display(ty))), elems)) + } + + function method TranslateSeqSelectExpr(se: C.SeqSelectExpr): (e: TranslationResult) + reads * + decreases ASTHeight(se), 0 + ensures e.Success? ==> P.All_Expr(e.value, DE.WellFormed) + { // FIXME: The models that we generate do not allow for `null` + var ty :- TranslateType(se.Seq.Type); + :- Need(ty.Collection? && !ty.kind.Set?, + Invalid("`SeqSelect` must be on a map, sequence, or multiset.")); + :- Need(se.SelectOne ==> se.E0 != null && se.E1 == null, + Invalid("Inconsistent values for `SelectOne` and E1 in SeqSelect.")); + :- Need(!se.SelectOne ==> ty.kind.Seq?, + Invalid("`SeqSelect` on a map or multiset must have a single index.")); + assume Math.Max(ASTHeight(se.Seq), Math.Max(ASTHeight(se.E0), ASTHeight(se.E1))) < ASTHeight(se); + var recv :- TranslateExpression(se.Seq); + var eager := (op, args) => Success(DE.Apply(DE.Eager(op), args)); + match ty.kind { // FIXME AST gen should produce `Expression?` not `Expression` + case Seq() => + if se.SelectOne then + assert se.E1 == null; + var e0 :- TranslateExpression(se.E0); + eager(DE.BinaryOp(DE.BinaryOps.Sequences(DE.BinaryOps.SeqSelect)), [recv, e0]) + else if se.E1 == null then + var e0 :- TranslateExpression(se.E0); + eager(DE.BinaryOp(DE.BinaryOps.Sequences(DE.BinaryOps.SeqDrop)), [recv, e0]) + else if se.E0 == null then + var e1 :- TranslateExpression(se.E1); + eager(DE.BinaryOp(DE.BinaryOps.Sequences(DE.BinaryOps.SeqTake)), [recv, e1]) + else + var e0 :- TranslateExpression(se.E0); + var e1 :- TranslateExpression(se.E1); + eager(DE.TernaryOp(DE.TernaryOps.Sequences(DE.TernaryOps.SeqSubseq)), [recv, e0, e1]) + case Map(_) => + assert se.SelectOne && se.E1 == null; + var e0 :- TranslateExpression(se.E0); + eager(DE.BinaryOp(DE.BinaryOps.Maps(DE.BinaryOps.MapSelect)), [recv, e0]) + case Multiset() => + assert se.SelectOne && se.E1 == null; + var e0 :- TranslateExpression(se.E0); + eager(DE.BinaryOp(DE.BinaryOps.Multisets(DE.BinaryOps.MultisetSelect)), [recv, e0]) + } + } + + function method TranslateSeqUpdateExpr(se: C.SeqUpdateExpr) + : (e: TranslationResult) + reads * + decreases ASTHeight(se), 0 + { + var ty :- TranslateType(se.Type); + :- Need(ty.Collection? && ty.kind != DT.Set(), + Invalid("`SeqUpdate` must be a map, sequence, or multiset.")); + assume Math.Max(ASTHeight(se.Seq), Math.Max(ASTHeight(se.Index), ASTHeight(se.Value))) < ASTHeight(se); + var tSeq :- TranslateExpression(se.Seq); + var tIndex :- TranslateExpression(se.Index); + var tValue :- TranslateExpression(se.Value); + var op := match ty.kind + case Seq() => DE.TernaryOps.Sequences(DE.TernaryOps.SeqUpdate) + case Map(_) => DE.TernaryOps.Maps(DE.TernaryOps.MapUpdate) + case Multiset() => DE.TernaryOps.Multisets(DE.TernaryOps.MultisetUpdate); + Success(DE.Apply(DE.Eager(DE.TernaryOp(op)), [tSeq, tIndex, tValue])) + } + + function method TranslateLambdaExpr(le: C.LambdaExpr) + : (e: TranslationResult) + reads * + decreases ASTHeight(le), 0 + { + var bvars := Seq.Map((bv: C.BoundVar) reads * => TypeConv.AsString(bv.Name), + ListUtils.ToSeq(le.BoundVars)); + assume Decreases(le.Term, le); + var body :- TranslateExpression(le.Term); + Success(DE.Abs(bvars, body)) + } + + function method TranslateLetExpr(le: C.LetExpr) + : (e: TranslationResult) + reads * + decreases ASTHeight(le), 0 + { + :- Need(le.Exact, UnsupportedExpr(le)); + var lhss := ListUtils.ToSeq(le.LHSs); + var bvs :- Seq.MapResult(lhss, (pat: C.CasePattern) reads * => + :- Need(pat.Var != null, UnsupportedExpr(le)); + Success(TypeConv.AsString(pat.Var.Name))); + var rhss := ListUtils.ToSeq(le.RHSs); + var elems :- Seq.MapResult(rhss, e requires e in rhss reads * => + assume Decreases(e, le); TranslateExpression(e)); + :- Need(|bvs| == |elems|, UnsupportedExpr(le)); + assume Decreases(le.Body, le); + var body :- TranslateExpression(le.Body); + Success(DE.Bind(bvs, elems, body)) + } + + function method TranslateConcreteSyntaxExpression(ce: C.ConcreteSyntaxExpression) + : (e: TranslationResult) + reads * + decreases ASTHeight(ce), 0 + { + assume Decreases(ce.ResolvedExpression, ce); + TranslateExpression(ce.ResolvedExpression) + } + + function method TranslateITEExpr(ie: C.ITEExpr) + : (e: TranslationResult) + reads * + decreases ASTHeight(ie), 0 + { + // TODO: look at i.IsBindingGuard + assume Decreases(ie.Test, ie); + assume Decreases(ie.Thn, ie); + assume Decreases(ie.Els, ie); + var cond :- TranslateExpression(ie.Test); + var thn :- TranslateExpression(ie.Thn); + var els :- TranslateExpression(ie.Els); + Success(DE.If(cond, thn, els)) + } + + function method TranslateExpression(c: C.Expression) + : (e: TranslationResult) + reads * + decreases ASTHeight(c), 2 + { + if c is C.IdentifierExpr then + TranslateIdentifierExpr(c as C.IdentifierExpr) + else if c is C.UnaryExpr then + TranslateUnary(c as C.UnaryExpr) + else if c is C.BinaryExpr then + TranslateBinary(c as C.BinaryExpr) + else if c is C.LiteralExpr then + TranslateLiteral(c as C.LiteralExpr) + else if c is C.ApplyExpr then + TranslateApplyExpr(c as C.ApplyExpr) + else if c is C.MemberSelectExpr then + TranslateMemberSelectExpr(c as C.MemberSelectExpr) + else if c is C.FunctionCallExpr then + TranslateFunctionCallExpr(c as C.FunctionCallExpr) + else if c is C.DatatypeValue then + TranslateDatatypeValue(c as C.DatatypeValue) + else if c is C.MapDisplayExpr then + TranslateMapDisplayExpr(c as C.MapDisplayExpr) + else if c is C.DisplayExpression then + TranslateDisplayExpr(c as C.DisplayExpression) + else if c is C.SeqUpdateExpr then + TranslateSeqUpdateExpr(c as C.SeqUpdateExpr) + else if c is C.SeqSelectExpr then + TranslateSeqSelectExpr(c as C.SeqSelectExpr) + else if c is C.LambdaExpr then + TranslateLambdaExpr(c as C.LambdaExpr) + else if c is C.LetExpr then + TranslateLetExpr(c as C.LetExpr) + else if c is C.ITEExpr then + TranslateITEExpr(c as C.ITEExpr) + else if c is C.ConcreteSyntaxExpression then + TranslateConcreteSyntaxExpression(c as C.ConcreteSyntaxExpression) + else Failure(UnsupportedExpr(c)) + } + + function method TranslatePrintStmt(p: C.PrintStmt) + : (e: TranslationResult) + reads * + decreases ASTHeight(p), 0 + { + var exprs :- Seq.MapResult(ListUtils.ToSeq(p.Args), TranslateExpression); + Success(DE.Apply(DE.Eager(DE.Builtin(DE.Print)), exprs)) + } + + function method TranslateBlockStmt(b: C.BlockStmt) + : (e: TranslationResult) + reads * + decreases ASTHeight(b), 0 + { + var stmts := ListUtils.ToSeq(b.Body); + var stmts' :- Seq.MapResult(stmts, s' requires s' in stmts reads * => + assume Decreases(s', b); TranslateStatement(s')); + Success(DE.Block(stmts')) + } + + function method TranslateIfStmt(i: C.IfStmt) + : (e: TranslationResult) + reads * + decreases ASTHeight(i), 0 + { + // TODO: look at i.IsBindingGuard + assume Decreases(i.Guard, i); + assume Decreases(i.Thn, i); + assume Decreases(i.Els, i); + var cond :- TranslateExpression(i.Guard); + var thn :- TranslateStatement(i.Thn); + var els :- TranslateStatement(i.Els); + Success(DE.If(cond, thn, els)) + } + + function method TranslateStatement(s: C.Statement) + : TranslationResult + reads * + decreases ASTHeight(s), 1 + { + if s is C.PrintStmt then + TranslatePrintStmt(s as C.PrintStmt) + else if s is C.BlockStmt then + TranslateBlockStmt(s as C.BlockStmt) + else if s is C.IfStmt then + TranslateIfStmt(s as C.IfStmt) + else Failure(UnsupportedStmt(s)) + } + + function method TranslateMethod(m: C.Method) + : TranslationResult + reads * + { + // var compileName := m.CompileName; + // FIXME “Main” + var stmts :- Seq.MapResult(ListUtils.ToSeq(m.Body.Body), TranslateStatement); + Success(D.Method("Main", DE.Block(stmts))) + } + + function method TranslateProgram(p: C.Program) + : TranslationResult + reads * + { + var tm :- TranslateMethod(p.MainMethod); + Success(D.Program(tm)) + } +} From c731cd326d1005a965742f5858d8e3aae8f2b4d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Fri, 22 Jul 2022 16:04:15 -0700 Subject: [PATCH 003/105] ast(wip): Model entities --- src/AST/Entities.dfy | 83 ++++++++++++++++++++++++++++++++++++++++++++ src/AST/Names.dfy | 16 +++++++++ 2 files changed, 99 insertions(+) create mode 100644 src/AST/Entities.dfy create mode 100644 src/AST/Names.dfy diff --git a/src/AST/Entities.dfy b/src/AST/Entities.dfy new file mode 100644 index 00000000..a1bc9e7a --- /dev/null +++ b/src/AST/Entities.dfy @@ -0,0 +1,83 @@ +/// Dafny's modules, types, and members hierarchy +/// ============================================= + +include "Names.dfy" +include "Syntax.dfy" +include "../Utils/Library.dfy" + +module Bootstrap.AST.Entities + // Hierarchies of Dafny entities. + // See . +{ + import opened Names + import opened Syntax.Exprs + import opened Utils.Lib.Datatypes + + datatype Module = + Module(members: seq) + + datatype ExportSet = + ExportSet(provided: set, revealed: set) + + datatype Import = + Import(localName: Atom, target: Name) + + datatype SubsetType = + SubsetType(boundVar: string, pred: Expr, witnessExpr: Expr) + + datatype TypeAlias = + TypeAlias(base: Name) + datatype AbstractType = + AbstractType() + datatype TraitType = + TraitType(parentTypes: seq) + datatype ClassType = + ClassType(parentTypes: seq) + datatype DataType = + DataType() + datatype NewType = + NewType() + + datatype Type = + | SubsetType(st: SubsetType) + | TypeAlias(ta: TypeAlias) + | AbstractType(at: AbstractType) + | TraitType(tt: TraitType) + | ClassType(ct: ClassType) + | DataType(dt: DataType) + | NewType(nt: NewType) + + datatype Field = + | Field(tp: Type) + + datatype FuncKind = + Method | Function | Constructor + datatype Func = + | Func(kind: FuncKind, body: Expr) + + datatype RawEntity = + | Module(children: seq) + | ExportSet(provided: set, revealed: set) + | Import(localName: Atom, target: Name) + | Type(children: seq) + | Definition() + + datatype Attribute = + Attribute(name: string, value: Expr) + + datatype Entity = + Entity(e: RawEntity, attrs: seq, children: seq) + + datatype NamedEntity = + NamedEntity(n: Name, e: Entity) + + datatype Registry = Registry(entities: map) { + function method Find(n: Name) : Option { + if n in entities then Some(entities[n]) else None + } + + function method Map(f: NamedEntity -> Entity) : Registry { + Registry(map n | n in entities :: f(NamedEntity(n, entities[n]))) + } + } +} diff --git a/src/AST/Names.dfy b/src/AST/Names.dfy new file mode 100644 index 00000000..9a2cdf03 --- /dev/null +++ b/src/AST/Names.dfy @@ -0,0 +1,16 @@ +module Bootstrap.AST.Names { + type Atom = + // An individual component of a Dafny name. + s: string | s != "" && '.' !in s witness "n" + + datatype Name = + | Anonymous + | Name(parent: Name, suffix: Atom) + { + predicate method Extends(parent: Name) + // Check whether `this` is a descendant of `parent`. + { + this.Name? && this.parent == parent + } + } +} From a62208cda742183d046c069ec94a64f8a56a7989 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Mon, 15 Aug 2022 18:12:16 -0700 Subject: [PATCH 004/105] src: Move common parts of the translator to `Translator.Common` --- src/AST/Translator.Common.dfy | 544 +----------------- ...nslator.dfy => Translator.Expressions.dfy} | 30 +- 2 files changed, 3 insertions(+), 571 deletions(-) rename src/AST/{Translator.dfy => Translator.Expressions.dfy} (96%) diff --git a/src/AST/Translator.Common.dfy b/src/AST/Translator.Common.dfy index c01b50e4..7f092af5 100644 --- a/src/AST/Translator.Common.dfy +++ b/src/AST/Translator.Common.dfy @@ -1,24 +1,11 @@ include "../Interop/CSharpDafnyASTModel.dfy" -include "../Interop/CSharpInterop.dfy" include "../Interop/CSharpDafnyInterop.dfy" -include "../Interop/CSharpDafnyASTInterop.dfy" include "../Utils/Library.dfy" -include "Syntax.dfy" -include "Predicates.dfy" -module Bootstrap.AST.Translator { - import opened Utils.Lib +module Bootstrap.AST.Translator.Common { import opened Utils.Lib.Datatypes - import opened Interop.CSharpInterop - import opened Interop.CSharpInterop.System import opened Interop.CSharpDafnyInterop - import opened Interop.CSharpDafnyInterop.Microsoft - import opened Interop.CSharpDafnyASTInterop import C = Interop.CSharpDafnyASTModel - import D = Syntax - import DE = Syntax.Exprs - import DT = Syntax.Types - import P = Predicates.Deep datatype TranslationError = | Invalid(msg: string) @@ -45,535 +32,6 @@ module Bootstrap.AST.Translator { } } - type Expr = e: DE.Expr | P.All_Expr(e, DE.WellFormed) - witness DE.Block([]) - type TranslationResult<+A> = Result - - function method TranslateType(ty: C.Type) - : TranslationResult - reads * - decreases TypeHeight(ty) - { - var ty := TypeUtils.NormalizeExpand(ty); - if ty is C.BoolType then - Success(DT.Bool) - else if ty is C.CharType then - Success(DT.Char) - else if ty is C.IntType then - Success(DT.Int) - else if ty is C.RealType then - Success(DT.Real) - else if ty is C.BigOrdinalType then - Success(DT.BigOrdinal) - else if ty is C.BitvectorType then - var bvTy := ty as C.BitvectorType; - :- Need(bvTy.Width >= 0, Invalid("BV width must be >= 0")); - Success(DT.BitVector(bvTy.Width as int)) - // TODO: the following could be simplified - else if ty is C.MapType then - var ty := ty as C.MapType; - assume TypeHeight(ty.Domain) < TypeHeight(ty); - assume TypeHeight(ty.Range) < TypeHeight(ty); - var domainTy :- TranslateType(ty.Domain); - var rangeTy :- TranslateType(ty.Range); - Success(DT.Collection(ty.Finite, DT.CollectionKind.Map(domainTy), rangeTy)) - else if ty is C.SetType then - var ty := ty as C.SetType; - assume TypeHeight(ty.Arg) < TypeHeight(ty); - var eltTy :- TranslateType(ty.Arg); - Success(DT.Collection(ty.Finite, DT.CollectionKind.Set, eltTy)) - else if ty is C.MultiSetType then - var ty := ty as C.MultiSetType; - assume TypeHeight(ty.Arg) < TypeHeight(ty); - var eltTy :- TranslateType(ty.Arg); - Success(DT.Collection(true, DT.CollectionKind.Multiset, eltTy)) - else if ty is C.SeqType then - var ty := ty as C.SeqType; - assume TypeHeight(ty.Arg) < TypeHeight(ty); - var eltTy :- TranslateType(ty.Arg); - Success(DT.Collection(true, DT.CollectionKind.Seq, eltTy)) - else - Failure(UnsupportedType(ty)) - } - - const GhostUnaryOps: set := - {C.UnaryOpExpr__ResolvedOpcode.Fresh, - C.UnaryOpExpr__ResolvedOpcode.Allocated, - C.UnaryOpExpr__ResolvedOpcode.Lit} - - const UnaryOpMap: map := - map[C.UnaryOpExpr__ResolvedOpcode.BVNot := D.UnaryOps.BVNot, - C.UnaryOpExpr__ResolvedOpcode.BoolNot := D.UnaryOps.BoolNot, - C.UnaryOpExpr__ResolvedOpcode.SeqLength := D.UnaryOps.SeqLength, - C.UnaryOpExpr__ResolvedOpcode.SetCard := D.UnaryOps.SetCard, - C.UnaryOpExpr__ResolvedOpcode.MultiSetCard := D.UnaryOps.MultisetCard, - C.UnaryOpExpr__ResolvedOpcode.MapCard := D.UnaryOps.MapCard] - - const LazyBinopMap: map := - map[C.BinaryExpr__ResolvedOpcode.Imp := DE.Imp, - C.BinaryExpr__ResolvedOpcode.And := DE.And, - C.BinaryExpr__ResolvedOpcode.Or := DE.Or] - - const EagerBinopMap: map := - map[C.BinaryExpr__ResolvedOpcode.Iff := D.BinaryOps.Logical(D.BinaryOps.Iff), - C.BinaryExpr__ResolvedOpcode.EqCommon := D.BinaryOps.Eq(D.BinaryOps.EqCommon), - C.BinaryExpr__ResolvedOpcode.NeqCommon := D.BinaryOps.Eq(D.BinaryOps.NeqCommon), - C.BinaryExpr__ResolvedOpcode.Lt := D.BinaryOps.Numeric(D.BinaryOps.Lt), - C.BinaryExpr__ResolvedOpcode.Le := D.BinaryOps.Numeric(D.BinaryOps.Le), - C.BinaryExpr__ResolvedOpcode.Ge := D.BinaryOps.Numeric(D.BinaryOps.Ge), - C.BinaryExpr__ResolvedOpcode.Gt := D.BinaryOps.Numeric(D.BinaryOps.Gt), - C.BinaryExpr__ResolvedOpcode.Add := D.BinaryOps.Numeric(D.BinaryOps.Add), - C.BinaryExpr__ResolvedOpcode.Sub := D.BinaryOps.Numeric(D.BinaryOps.Sub), - C.BinaryExpr__ResolvedOpcode.Mul := D.BinaryOps.Numeric(D.BinaryOps.Mul), - C.BinaryExpr__ResolvedOpcode.Div := D.BinaryOps.Numeric(D.BinaryOps.Div), - C.BinaryExpr__ResolvedOpcode.Mod := D.BinaryOps.Numeric(D.BinaryOps.Mod), - C.BinaryExpr__ResolvedOpcode.LeftShift := D.BinaryOps.BV(D.BinaryOps.LeftShift), - C.BinaryExpr__ResolvedOpcode.RightShift := D.BinaryOps.BV(D.BinaryOps.RightShift), - C.BinaryExpr__ResolvedOpcode.BitwiseAnd := D.BinaryOps.BV(D.BinaryOps.BitwiseAnd), - C.BinaryExpr__ResolvedOpcode.BitwiseOr := D.BinaryOps.BV(D.BinaryOps.BitwiseOr), - C.BinaryExpr__ResolvedOpcode.BitwiseXor := D.BinaryOps.BV(D.BinaryOps.BitwiseXor), - C.BinaryExpr__ResolvedOpcode.LtChar := D.BinaryOps.Char(D.BinaryOps.LtChar), - C.BinaryExpr__ResolvedOpcode.LeChar := D.BinaryOps.Char(D.BinaryOps.LeChar), - C.BinaryExpr__ResolvedOpcode.GeChar := D.BinaryOps.Char(D.BinaryOps.GeChar), - C.BinaryExpr__ResolvedOpcode.GtChar := D.BinaryOps.Char(D.BinaryOps.GtChar), - C.BinaryExpr__ResolvedOpcode.SeqEq := D.BinaryOps.Sequences(D.BinaryOps.SeqEq), - C.BinaryExpr__ResolvedOpcode.SeqNeq := D.BinaryOps.Sequences(D.BinaryOps.SeqNeq), - C.BinaryExpr__ResolvedOpcode.ProperPrefix := D.BinaryOps.Sequences(D.BinaryOps.ProperPrefix), - C.BinaryExpr__ResolvedOpcode.Prefix := D.BinaryOps.Sequences(D.BinaryOps.Prefix), - C.BinaryExpr__ResolvedOpcode.Concat := D.BinaryOps.Sequences(D.BinaryOps.Concat), - C.BinaryExpr__ResolvedOpcode.InSeq := D.BinaryOps.Sequences(D.BinaryOps.InSeq), - C.BinaryExpr__ResolvedOpcode.NotInSeq := D.BinaryOps.Sequences(D.BinaryOps.NotInSeq), - C.BinaryExpr__ResolvedOpcode.SetEq := D.BinaryOps.Sets(D.BinaryOps.SetEq), - C.BinaryExpr__ResolvedOpcode.SetNeq := D.BinaryOps.Sets(D.BinaryOps.SetNeq), - C.BinaryExpr__ResolvedOpcode.ProperSubset := D.BinaryOps.Sets(D.BinaryOps.ProperSubset), - C.BinaryExpr__ResolvedOpcode.Subset := D.BinaryOps.Sets(D.BinaryOps.Subset), - C.BinaryExpr__ResolvedOpcode.Superset := D.BinaryOps.Sets(D.BinaryOps.Superset), - C.BinaryExpr__ResolvedOpcode.ProperSuperset := D.BinaryOps.Sets(D.BinaryOps.ProperSuperset), - C.BinaryExpr__ResolvedOpcode.Disjoint := D.BinaryOps.Sets(D.BinaryOps.Disjoint), - C.BinaryExpr__ResolvedOpcode.InSet := D.BinaryOps.Sets(D.BinaryOps.InSet), - C.BinaryExpr__ResolvedOpcode.NotInSet := D.BinaryOps.Sets(D.BinaryOps.NotInSet), - C.BinaryExpr__ResolvedOpcode.Union := D.BinaryOps.Sets(D.BinaryOps.Union), - C.BinaryExpr__ResolvedOpcode.Intersection := D.BinaryOps.Sets(D.BinaryOps.Intersection), - C.BinaryExpr__ResolvedOpcode.SetDifference := D.BinaryOps.Sets(D.BinaryOps.SetDifference), - C.BinaryExpr__ResolvedOpcode.MultiSetEq := D.BinaryOps.Multisets(D.BinaryOps.MultisetEq), - C.BinaryExpr__ResolvedOpcode.MultiSetNeq := D.BinaryOps.Multisets(D.BinaryOps.MultisetNeq), - C.BinaryExpr__ResolvedOpcode.MultiSubset := D.BinaryOps.Multisets(D.BinaryOps.MultiSubset), - C.BinaryExpr__ResolvedOpcode.MultiSuperset := D.BinaryOps.Multisets(D.BinaryOps.MultiSuperset), - C.BinaryExpr__ResolvedOpcode.ProperMultiSubset := D.BinaryOps.Multisets(D.BinaryOps.ProperMultiSubset), - C.BinaryExpr__ResolvedOpcode.ProperMultiSuperset := D.BinaryOps.Multisets(D.BinaryOps.ProperMultiSuperset), - C.BinaryExpr__ResolvedOpcode.MultiSetDisjoint := D.BinaryOps.Multisets(D.BinaryOps.MultisetDisjoint), - C.BinaryExpr__ResolvedOpcode.InMultiSet := D.BinaryOps.Multisets(D.BinaryOps.InMultiset), - C.BinaryExpr__ResolvedOpcode.NotInMultiSet := D.BinaryOps.Multisets(D.BinaryOps.NotInMultiset), - C.BinaryExpr__ResolvedOpcode.MultiSetUnion := D.BinaryOps.Multisets(D.BinaryOps.MultisetUnion), - C.BinaryExpr__ResolvedOpcode.MultiSetIntersection := D.BinaryOps.Multisets(D.BinaryOps.MultisetIntersection), - C.BinaryExpr__ResolvedOpcode.MultiSetDifference := D.BinaryOps.Multisets(D.BinaryOps.MultisetDifference), - C.BinaryExpr__ResolvedOpcode.MapEq := D.BinaryOps.Maps(D.BinaryOps.MapEq), - C.BinaryExpr__ResolvedOpcode.MapNeq := D.BinaryOps.Maps(D.BinaryOps.MapNeq), - C.BinaryExpr__ResolvedOpcode.InMap := D.BinaryOps.Maps(D.BinaryOps.InMap), - C.BinaryExpr__ResolvedOpcode.NotInMap := D.BinaryOps.Maps(D.BinaryOps.NotInMap), - C.BinaryExpr__ResolvedOpcode.MapMerge := D.BinaryOps.Maps(D.BinaryOps.MapMerge), - C.BinaryExpr__ResolvedOpcode.MapSubtraction := D.BinaryOps.Maps(D.BinaryOps.MapSubtraction), - C.BinaryExpr__ResolvedOpcode.RankLt := D.BinaryOps.Datatypes(D.BinaryOps.RankLt), - C.BinaryExpr__ResolvedOpcode.RankGt := D.BinaryOps.Datatypes(D.BinaryOps.RankGt)]; - - const BinaryOpCodeMap: map := - (map k | k in LazyBinopMap :: DE.Lazy(LazyBinopMap[k])) + - (map k | k in EagerBinopMap :: DE.Eager(DE.BinaryOp(EagerBinopMap[k]))) - - function method TranslateIdentifierExpr(ie: C.IdentifierExpr) - : (e: TranslationResult) - reads * - { - Success(DE.Var(TypeConv.AsString(ie.Name))) - } - - predicate Decreases(u: object, v: object) - requires u is C.Expression || u is C.Statement - requires v is C.Expression || v is C.Statement - { - ASTHeight(u) < ASTHeight(v) - } - - function method TranslateUnary(u: C.UnaryExpr) - : (e: TranslationResult) - decreases ASTHeight(u), 0 - reads * - { - :- Need(u is C.UnaryOpExpr, UnsupportedExpr(u)); - var u := u as C.UnaryOpExpr; - var op, e := u.ResolvedOp, u.E; - assume Decreases(e, u); - :- Need(op !in GhostUnaryOps, GhostExpr(u)); - :- Need(op in UnaryOpMap.Keys, UnsupportedExpr(u)); - var te :- TranslateExpression(e); - Success(DE.Apply(DE.Eager(DE.UnaryOp(UnaryOpMap[op])), [te])) - } - - function method TranslateBinary(b: C.BinaryExpr) - : (e: TranslationResult) - decreases ASTHeight(b), 0 - reads * - { - var op, e0, e1 := b.ResolvedOp, b.E0, b.E1; - // LATER b.AccumulatesForTailRecursion - assume Decreases(e0, b); - assume Decreases(e1, b); - :- Need(op in BinaryOpCodeMap, UnsupportedExpr(b)); - var t0 :- TranslateExpression(e0); - var t1 :- TranslateExpression(e1); - Success(DE.Apply(BinaryOpCodeMap[op], [t0, t1])) - } - - function method TranslateLiteral(l: C.LiteralExpr) - : (e: TranslationResult) - reads * - { - if l.Value is Boolean then - Success(DE.Literal(DE.LitBool(TypeConv.AsBool(l.Value)))) - else if l.Value is Numerics.BigInteger then - Success(DE.Literal(DE.LitInt(TypeConv.AsInt(l.Value)))) - else if l.Value is BaseTypes.BigDec then - Success(DE.Literal(DE.LitReal(TypeConv.AsReal(l.Value)))) // TODO test - else if l.Value is String then - var str := TypeConv.AsString(l.Value); - if l is C.CharLiteralExpr then - :- Need(|str| == 1, Invalid("CharLiteralExpr must contain a single character.")); - Success(DE.Literal(DE.LitChar(str[0]))) - else if l is C.StringLiteralExpr then - var sl := l as C.StringLiteralExpr; - Success(DE.Literal(DE.LitString(str, sl.IsVerbatim))) - else - Failure(Invalid("LiteralExpr with .Value of type string must be a char or a string.")) - else - Failure(UnsupportedExpr(l)) - } - - function method TranslateApplyExpr(ae: C.ApplyExpr) - : (e: TranslationResult) - reads * - decreases ASTHeight(ae), 0 - { - assume Decreases(ae.Function, ae); - var fn :- TranslateExpression(ae.Function); - var args := ListUtils.ToSeq(ae.Args); - var args :- Seq.MapResult(args, e requires e in args reads * => - assume Decreases(e, ae); TranslateExpression(e)); - Success(DE.Apply(DE.Eager(DE.FunctionCall()), [fn] + args)) - } - - function method TranslateMemberSelect(obj: C.Expression, fullName: System.String) - : (e: TranslationResult) - reads * - decreases ASTHeight(obj), 3 - { - var fname := TypeConv.AsString(fullName); - if obj.Resolved is C.StaticReceiverExpr then - Success(DE.Var(fname)) - else - var obj :- TranslateExpression(obj); - Success(DE.Apply(DE.Eager(DE.UnaryOp(DE.UnaryOps.MemberSelect(fname))), [obj])) - } - - function method TranslateMemberSelectExpr(me: C.MemberSelectExpr) - : (e: TranslationResult) - reads * - decreases ASTHeight(me), 0 - { - assume Decreases(me.Obj, me); - TranslateMemberSelect(me.Obj, me.Member.FullName) - } - - function method TranslateFunctionCallExpr(fce: C.FunctionCallExpr) - : (e: TranslationResult) - reads * - decreases ASTHeight(fce), 0 - { - assume Decreases(fce.Receiver, fce); - var fn :- TranslateMemberSelect(fce.Receiver, fce.Function.FullName); - var args := ListUtils.ToSeq(fce.Args); - var args :- Seq.MapResult(args, e requires e in args reads * => - assume Decreases(e, fce); TranslateExpression(e)); - Success(DE.Apply(DE.Eager(DE.FunctionCall()), [fn] + args)) - } - - function method TranslateDatatypeValue(dtv: C.DatatypeValue) - : (e: TranslationResult) - reads * - decreases ASTHeight(dtv), 0 - { - var ctor := dtv.Ctor; - var n := TypeConv.AsString(ctor.Name); - var typeArgs :- Seq.MapResult(ListUtils.ToSeq(dtv.InferredTypeArgs), TranslateType); - // TODO: also include formals in the following, and filter out ghost arguments - var args := ListUtils.ToSeq(dtv.Arguments); - var args :- Seq.MapResult(args, e requires e in args reads * => - assume Decreases(e, dtv); TranslateExpression(e)); - Success(DE.Apply(DE.Eager(DE.DataConstructor([n], typeArgs)), args)) // TODO: proper path - } - - function method TranslateDisplayExpr(de: C.DisplayExpression) - : (e: TranslationResult) - reads * - decreases ASTHeight(de), 0 - { - var ty :- TranslateType(de.Type); - :- Need(ty.Collection? && ty.finite, Invalid("`DisplayExpr` must be a finite collection.")); - var elems := ListUtils.ToSeq(de.Elements); - var elems :- Seq.MapResult(elems, e requires e in elems reads * => - assume Decreases(e, de); TranslateExpression(e)); - Success(DE.Apply(DE.Eager(DE.Builtin(DE.Display(ty))), elems)) - } - - function method TranslateExpressionPair(mde: C.MapDisplayExpr, ep: C.ExpressionPair) - : (e: TranslationResult) - reads * - requires Math.Max(ASTHeight(ep.A), ASTHeight(ep.B)) < ASTHeight(mde) - decreases ASTHeight(mde), 0 - { - var tyA :- TranslateType(ep.A.Type); - // TODO: This isn't really a sequence of type tyA! It should really construct pairs - var ty := DT.Collection(true, DT.CollectionKind.Seq, tyA); - var tA :- TranslateExpression(ep.A); - var tB :- TranslateExpression(ep.B); - Success(DE.Apply(DE.Eager(DE.Builtin(DE.Display(ty))), [tA, tB])) - } - - function method TranslateMapDisplayExpr(mde: C.MapDisplayExpr) - : (e: TranslationResult) - reads * - decreases ASTHeight(mde), 1 - { - var ty :- TranslateType(mde.Type); - :- Need(ty.Collection? && ty.kind.Map? && ty.finite, Invalid("`MapDisplayExpr` must be a map.")); - var elems := ListUtils.ToSeq(mde.Elements); - var elems :- Seq.MapResult(elems, (ep: C.ExpressionPair) requires ep in elems reads * => - assume Math.Max(ASTHeight(ep.A), ASTHeight(ep.B)) < ASTHeight(mde); - TranslateExpressionPair(mde, ep)); - Success(DE.Apply(DE.Eager(DE.Builtin(DE.Display(ty))), elems)) - } - - function method TranslateSeqSelectExpr(se: C.SeqSelectExpr): (e: TranslationResult) - reads * - decreases ASTHeight(se), 0 - ensures e.Success? ==> P.All_Expr(e.value, DE.WellFormed) - { // FIXME: The models that we generate do not allow for `null` - var ty :- TranslateType(se.Seq.Type); - :- Need(ty.Collection? && !ty.kind.Set?, - Invalid("`SeqSelect` must be on a map, sequence, or multiset.")); - :- Need(se.SelectOne ==> se.E0 != null && se.E1 == null, - Invalid("Inconsistent values for `SelectOne` and E1 in SeqSelect.")); - :- Need(!se.SelectOne ==> ty.kind.Seq?, - Invalid("`SeqSelect` on a map or multiset must have a single index.")); - assume Math.Max(ASTHeight(se.Seq), Math.Max(ASTHeight(se.E0), ASTHeight(se.E1))) < ASTHeight(se); - var recv :- TranslateExpression(se.Seq); - var eager := (op, args) => Success(DE.Apply(DE.Eager(op), args)); - match ty.kind { // FIXME AST gen should produce `Expression?` not `Expression` - case Seq() => - if se.SelectOne then - assert se.E1 == null; - var e0 :- TranslateExpression(se.E0); - eager(DE.BinaryOp(DE.BinaryOps.Sequences(DE.BinaryOps.SeqSelect)), [recv, e0]) - else if se.E1 == null then - var e0 :- TranslateExpression(se.E0); - eager(DE.BinaryOp(DE.BinaryOps.Sequences(DE.BinaryOps.SeqDrop)), [recv, e0]) - else if se.E0 == null then - var e1 :- TranslateExpression(se.E1); - eager(DE.BinaryOp(DE.BinaryOps.Sequences(DE.BinaryOps.SeqTake)), [recv, e1]) - else - var e0 :- TranslateExpression(se.E0); - var e1 :- TranslateExpression(se.E1); - eager(DE.TernaryOp(DE.TernaryOps.Sequences(DE.TernaryOps.SeqSubseq)), [recv, e0, e1]) - case Map(_) => - assert se.SelectOne && se.E1 == null; - var e0 :- TranslateExpression(se.E0); - eager(DE.BinaryOp(DE.BinaryOps.Maps(DE.BinaryOps.MapSelect)), [recv, e0]) - case Multiset() => - assert se.SelectOne && se.E1 == null; - var e0 :- TranslateExpression(se.E0); - eager(DE.BinaryOp(DE.BinaryOps.Multisets(DE.BinaryOps.MultisetSelect)), [recv, e0]) - } - } - - function method TranslateSeqUpdateExpr(se: C.SeqUpdateExpr) - : (e: TranslationResult) - reads * - decreases ASTHeight(se), 0 - { - var ty :- TranslateType(se.Type); - :- Need(ty.Collection? && ty.kind != DT.Set(), - Invalid("`SeqUpdate` must be a map, sequence, or multiset.")); - assume Math.Max(ASTHeight(se.Seq), Math.Max(ASTHeight(se.Index), ASTHeight(se.Value))) < ASTHeight(se); - var tSeq :- TranslateExpression(se.Seq); - var tIndex :- TranslateExpression(se.Index); - var tValue :- TranslateExpression(se.Value); - var op := match ty.kind - case Seq() => DE.TernaryOps.Sequences(DE.TernaryOps.SeqUpdate) - case Map(_) => DE.TernaryOps.Maps(DE.TernaryOps.MapUpdate) - case Multiset() => DE.TernaryOps.Multisets(DE.TernaryOps.MultisetUpdate); - Success(DE.Apply(DE.Eager(DE.TernaryOp(op)), [tSeq, tIndex, tValue])) - } - - function method TranslateLambdaExpr(le: C.LambdaExpr) - : (e: TranslationResult) - reads * - decreases ASTHeight(le), 0 - { - var bvars := Seq.Map((bv: C.BoundVar) reads * => TypeConv.AsString(bv.Name), - ListUtils.ToSeq(le.BoundVars)); - assume Decreases(le.Term, le); - var body :- TranslateExpression(le.Term); - Success(DE.Abs(bvars, body)) - } - - function method TranslateLetExpr(le: C.LetExpr) - : (e: TranslationResult) - reads * - decreases ASTHeight(le), 0 - { - :- Need(le.Exact, UnsupportedExpr(le)); - var lhss := ListUtils.ToSeq(le.LHSs); - var bvs :- Seq.MapResult(lhss, (pat: C.CasePattern) reads * => - :- Need(pat.Var != null, UnsupportedExpr(le)); - Success(TypeConv.AsString(pat.Var.Name))); - var rhss := ListUtils.ToSeq(le.RHSs); - var elems :- Seq.MapResult(rhss, e requires e in rhss reads * => - assume Decreases(e, le); TranslateExpression(e)); - :- Need(|bvs| == |elems|, UnsupportedExpr(le)); - assume Decreases(le.Body, le); - var body :- TranslateExpression(le.Body); - Success(DE.Bind(bvs, elems, body)) - } - - function method TranslateConcreteSyntaxExpression(ce: C.ConcreteSyntaxExpression) - : (e: TranslationResult) - reads * - decreases ASTHeight(ce), 0 - { - assume Decreases(ce.ResolvedExpression, ce); - TranslateExpression(ce.ResolvedExpression) - } - - function method TranslateITEExpr(ie: C.ITEExpr) - : (e: TranslationResult) - reads * - decreases ASTHeight(ie), 0 - { - // TODO: look at i.IsBindingGuard - assume Decreases(ie.Test, ie); - assume Decreases(ie.Thn, ie); - assume Decreases(ie.Els, ie); - var cond :- TranslateExpression(ie.Test); - var thn :- TranslateExpression(ie.Thn); - var els :- TranslateExpression(ie.Els); - Success(DE.If(cond, thn, els)) - } - - function method TranslateExpression(c: C.Expression) - : (e: TranslationResult) - reads * - decreases ASTHeight(c), 2 - { - if c is C.IdentifierExpr then - TranslateIdentifierExpr(c as C.IdentifierExpr) - else if c is C.UnaryExpr then - TranslateUnary(c as C.UnaryExpr) - else if c is C.BinaryExpr then - TranslateBinary(c as C.BinaryExpr) - else if c is C.LiteralExpr then - TranslateLiteral(c as C.LiteralExpr) - else if c is C.ApplyExpr then - TranslateApplyExpr(c as C.ApplyExpr) - else if c is C.MemberSelectExpr then - TranslateMemberSelectExpr(c as C.MemberSelectExpr) - else if c is C.FunctionCallExpr then - TranslateFunctionCallExpr(c as C.FunctionCallExpr) - else if c is C.DatatypeValue then - TranslateDatatypeValue(c as C.DatatypeValue) - else if c is C.MapDisplayExpr then - TranslateMapDisplayExpr(c as C.MapDisplayExpr) - else if c is C.DisplayExpression then - TranslateDisplayExpr(c as C.DisplayExpression) - else if c is C.SeqUpdateExpr then - TranslateSeqUpdateExpr(c as C.SeqUpdateExpr) - else if c is C.SeqSelectExpr then - TranslateSeqSelectExpr(c as C.SeqSelectExpr) - else if c is C.LambdaExpr then - TranslateLambdaExpr(c as C.LambdaExpr) - else if c is C.LetExpr then - TranslateLetExpr(c as C.LetExpr) - else if c is C.ITEExpr then - TranslateITEExpr(c as C.ITEExpr) - else if c is C.ConcreteSyntaxExpression then - TranslateConcreteSyntaxExpression(c as C.ConcreteSyntaxExpression) - else Failure(UnsupportedExpr(c)) - } - - function method TranslatePrintStmt(p: C.PrintStmt) - : (e: TranslationResult) - reads * - decreases ASTHeight(p), 0 - { - var exprs :- Seq.MapResult(ListUtils.ToSeq(p.Args), TranslateExpression); - Success(DE.Apply(DE.Eager(DE.Builtin(DE.Print)), exprs)) - } - - function method TranslateBlockStmt(b: C.BlockStmt) - : (e: TranslationResult) - reads * - decreases ASTHeight(b), 0 - { - var stmts := ListUtils.ToSeq(b.Body); - var stmts' :- Seq.MapResult(stmts, s' requires s' in stmts reads * => - assume Decreases(s', b); TranslateStatement(s')); - Success(DE.Block(stmts')) - } - - function method TranslateIfStmt(i: C.IfStmt) - : (e: TranslationResult) - reads * - decreases ASTHeight(i), 0 - { - // TODO: look at i.IsBindingGuard - assume Decreases(i.Guard, i); - assume Decreases(i.Thn, i); - assume Decreases(i.Els, i); - var cond :- TranslateExpression(i.Guard); - var thn :- TranslateStatement(i.Thn); - var els :- TranslateStatement(i.Els); - Success(DE.If(cond, thn, els)) - } - - function method TranslateStatement(s: C.Statement) - : TranslationResult - reads * - decreases ASTHeight(s), 1 - { - if s is C.PrintStmt then - TranslatePrintStmt(s as C.PrintStmt) - else if s is C.BlockStmt then - TranslateBlockStmt(s as C.BlockStmt) - else if s is C.IfStmt then - TranslateIfStmt(s as C.IfStmt) - else Failure(UnsupportedStmt(s)) - } - - function method TranslateMethod(m: C.Method) - : TranslationResult - reads * - { - // var compileName := m.CompileName; - // FIXME “Main” - var stmts :- Seq.MapResult(ListUtils.ToSeq(m.Body.Body), TranslateStatement); - Success(D.Method("Main", DE.Block(stmts))) - } - - function method TranslateProgram(p: C.Program) - : TranslationResult - reads * - { - var tm :- TranslateMethod(p.MainMethod); - Success(D.Program(tm)) - } } diff --git a/src/AST/Translator.dfy b/src/AST/Translator.Expressions.dfy similarity index 96% rename from src/AST/Translator.dfy rename to src/AST/Translator.Expressions.dfy index c01b50e4..3149aac3 100644 --- a/src/AST/Translator.dfy +++ b/src/AST/Translator.Expressions.dfy @@ -5,6 +5,7 @@ include "../Interop/CSharpDafnyASTInterop.dfy" include "../Utils/Library.dfy" include "Syntax.dfy" include "Predicates.dfy" +include "Translator.Common.dfy" module Bootstrap.AST.Translator { import opened Utils.Lib @@ -19,38 +20,11 @@ module Bootstrap.AST.Translator { import DE = Syntax.Exprs import DT = Syntax.Types import P = Predicates.Deep - - datatype TranslationError = - | Invalid(msg: string) - | GhostExpr(expr: C.Expression) - | UnsupportedType(ty: C.Type) - | UnsupportedExpr(expr: C.Expression) - | UnsupportedStmt(stmt: C.Statement) - | UnsupportedMember(decl: C.MemberDecl) - { - function method ToString() : string { - match this - case Invalid(msg) => - "Invalid term: " + msg - case GhostExpr(expr) => - "Ghost expression: " + TypeConv.ObjectToString(expr) - case UnsupportedType(ty) => - "Unsupported type: " + TypeConv.ObjectToString(ty) - case UnsupportedExpr(expr) => - "Unsupported expression: " + TypeConv.ObjectToString(expr) - case UnsupportedStmt(stmt) => - "Unsupported statement: " + TypeConv.ObjectToString(stmt) - case UnsupportedMember(decl) => - "Unsupported declaration: " + TypeConv.ObjectToString(decl) - } - } + import opened Common type Expr = e: DE.Expr | P.All_Expr(e, DE.WellFormed) witness DE.Block([]) - type TranslationResult<+A> = - Result - function method TranslateType(ty: C.Type) : TranslationResult reads * From d14b6d3a98d42bf8df147160c802efad9bf23a9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Mon, 15 Aug 2022 20:49:55 -0700 Subject: [PATCH 005/105] ast: Refine definition of entities --- src/AST/Entities.dfy | 158 +++++++++++++++++++++++++++++++++++++------ src/AST/Names.dfy | 57 ++++++++++++++-- src/AST/Syntax.dfy | 17 ----- 3 files changed, 189 insertions(+), 43 deletions(-) diff --git a/src/AST/Entities.dfy b/src/AST/Entities.dfy index a1bc9e7a..09fb1838 100644 --- a/src/AST/Entities.dfy +++ b/src/AST/Entities.dfy @@ -5,7 +5,7 @@ include "Names.dfy" include "Syntax.dfy" include "../Utils/Library.dfy" -module Bootstrap.AST.Entities +module {:options "-functionSyntax:4"} Bootstrap.AST.Entities // Hierarchies of Dafny entities. // See . { @@ -47,37 +47,151 @@ module Bootstrap.AST.Entities | DataType(dt: DataType) | NewType(nt: NewType) + datatype FieldKind = + Const | Var datatype Field = - | Field(tp: Type) + Field(kind: FieldKind, body: Expr) + + datatype Callable = + | Method(body: Expr) + | Function(body: Expr) + | Constructor(body: Expr) + + datatype Definition = + | Field(fi: Field) + | Callable(ci: Callable) + + datatype EntityKind = + | EModule + | EExportSet + | EImport + | EType + | EDefinition + + type EntityInfo = e: EntityInfo_ | e.Valid?() + witness EntityInfo(Anonymous, attrs := [], members := []) + datatype EntityInfo_ = + EntityInfo(name: Name, nameonly attrs: seq, nameonly members: seq) + { + ghost predicate Valid?() { + forall nm <- members :: nm.ChildOf(name) + } + } - datatype FuncKind = - Method | Function | Constructor - datatype Func = - | Func(kind: FuncKind, body: Expr) + datatype Entity = + | Module(ei: EntityInfo) + | ExportSet(ei: EntityInfo, e: ExportSet) + | Import(ei: EntityInfo, i: Import) + | Type(ei: EntityInfo, t: Type) + | Definition(ei: EntityInfo, d: Definition) + { + const kind := + match this + case Module(ei) => EModule + case ExportSet(ei, e) => EExportSet + case Import(ei, i) => EImport + case Type(ei, t) => EType + case Definition(ei, d) => EDefinition + } - datatype RawEntity = - | Module(children: seq) - | ExportSet(provided: set, revealed: set) - | Import(localName: Atom, target: Name) - | Type(children: seq) - | Definition() + datatype Attribute = // TODO: Move all exprs to top level? + Attribute(name: string, args: seq) + { + function ToString(): string { + "{:" + name + (if args != [] then " ..." else "") + "}" + } + } - datatype Attribute = - Attribute(name: string, value: Expr) + ghost predicate EntityMap?(f: Entity -> Entity) { + forall e :: + && f(e).kind == e.kind + && f(e).ei.name == e.ei.name + && f(e).ei.members == e.ei.members + } - datatype Entity = - Entity(e: RawEntity, attrs: seq, children: seq) + type EntityMap = f | EntityMap?(f) witness e => e + + type Registry = r: Registry_ | r.Valid?() + witness Registry(map[]) + datatype Registry_ = Registry(entities: map) + // A collection of Dafny entities, index by name. + // + // The entity graph of a Dafny program is a tree: members of a module or + // class have names that extend that of the parent module. This fact allows + // easy recursion, using the functions `SuffixesOf` and `SuffixesOfMany` + // below, along with the two recursion lemmas `Decreases_SuffixesOf` and + // `Decreases_SuffixesOfMany`. + { + ghost predicate ValidNames?() { + forall n <- entities :: entities[n].ei.name == n + } + + ghost predicate ValidParent??(n: Name) { + n == Anonymous || n.parent in entities + } + + ghost predicate ValidParents?() { + forall n <- entities :: ValidParent??(n) + } - datatype NamedEntity = - NamedEntity(n: Name, e: Entity) + ghost predicate ValidMembers??(ei: EntityInfo) { + forall m <- ei.members :: m in entities + } + + ghost predicate ValidMembers?() { + forall n <- entities :: ValidMembers??(entities[n].ei) + } + + ghost predicate Valid?() { + && ValidNames?() + && ValidParents?() + && ValidMembers?() + } + + ghost function {:opaque} SuffixesOf(name: Name): set { + set n <- entities | n.SuffixOf(name) + } + + ghost function {:opaque} SuffixesOfMany(names: seq): set { + set n <- names, sf <- SuffixesOf(n) :: sf + } + + lemma Decreases_SuffixesOfMany(ei: EntityInfo) + requires ei.name in entities + ensures SuffixesOfMany(ei.members) < SuffixesOf(ei.name); + { + reveal SuffixesOf(); + reveal SuffixesOfMany(); + + assert SuffixesOfMany(ei.members) <= SuffixesOf(ei.name) by { + forall sf <- SuffixesOfMany(ei.members) + ensures sf in SuffixesOf(ei.name) + { + var name: Name :| name in ei.members && sf in SuffixesOf(name); + Name.SuffixOf_Transitive(ei.name, name, sf); + } + } + + assert ei.name in SuffixesOf(ei.name); + assert ei.name !in SuffixesOfMany(ei.members); + } + + lemma {:induction false} Decreases_SuffixesOf(names: seq, name: Name) + requires name in names + ensures SuffixesOf(name) <= SuffixesOfMany(names); + { + reveal SuffixesOf(); + reveal SuffixesOfMany(); + } - datatype Registry = Registry(entities: map) { - function method Find(n: Name) : Option { + function Find(n: Name) : Option { if n in entities then Some(entities[n]) else None } - function method Map(f: NamedEntity -> Entity) : Registry { - Registry(map n | n in entities :: f(NamedEntity(n, entities[n]))) + function Map(f: EntityMap) : Registry + requires Valid?() + { + Registry(map n | n in entities :: f(entities[n])) } } } diff --git a/src/AST/Names.dfy b/src/AST/Names.dfy index 9a2cdf03..ff6604ca 100644 --- a/src/AST/Names.dfy +++ b/src/AST/Names.dfy @@ -1,4 +1,4 @@ -module Bootstrap.AST.Names { +module {:options "-functionSyntax:4"} Bootstrap.AST.Names { type Atom = // An individual component of a Dafny name. s: string | s != "" && '.' !in s witness "n" @@ -7,10 +7,59 @@ module Bootstrap.AST.Names { | Anonymous | Name(parent: Name, suffix: Atom) { - predicate method Extends(parent: Name) - // Check whether `this` is a descendant of `parent`. + function Length(): nat { + match this + case Anonymous => 0 + case Name(_, _) => 1 + parent.Length() + } + + function ToString(): string { + match this + case Anonymous => "_" + case Name(Anonymous, suffix) => suffix + case Name(parent, suffix) => parent.ToString() + "." + suffix + } + + predicate ChildOf(parent: Name) + // Check whether `this` is a direct descendant of `parent`. + ensures ChildOf(parent) ==> Length() == parent.Length() + 1 + { + this.Name? && parent == this.parent + } + + predicate StrictSuffixOf(parent: Name) + // Check whether one of the parents of `this` is `parent`. + ensures StrictSuffixOf(parent) ==> Length() > parent.Length() + { + this != parent && SuffixOf(parent) + } + + predicate SuffixOf(parent: Name) + // Check whether `this` descended from `parent`. + decreases this + ensures SuffixOf(parent) ==> Length() >= parent.Length() + { + || parent == this + || (this.Name? && this.parent.SuffixOf(parent)) + } + + static lemma {:induction n2} SuffixOf_Transitive(n0: Name, n1: Name, n2: Name) + requires n2.SuffixOf(n1) && n1.SuffixOf(n0) + ensures n2.SuffixOf(n0) + {} + + static lemma {:induction false} StrictSuffixOf_Left_Transitive(n0: Name, n1: Name, n2: Name) + requires n2.StrictSuffixOf(n1) && n1.SuffixOf(n0) + ensures n2.StrictSuffixOf(n0) + { + SuffixOf_Transitive(n0, n1, n2); + } + + static lemma {:induction false} StrictSuffixOf_Right_Transitive(n0: Name, n1: Name, n2: Name) + requires n2.SuffixOf(n1) && n1.StrictSuffixOf(n0) + ensures n2.StrictSuffixOf(n0) { - this.Name? && this.parent == parent + SuffixOf_Transitive(n0, n1, n2); } } } diff --git a/src/AST/Syntax.dfy b/src/AST/Syntax.dfy index 4e5c025e..00ad50be 100644 --- a/src/AST/Syntax.dfy +++ b/src/AST/Syntax.dfy @@ -331,21 +331,4 @@ module Exprs { } type Expr = Exprs.T - - datatype Method = Method(CompileName: string, methodBody: Exprs.T) { - function method Depth() : nat { - 1 + match this { - case Method(CompileName, methodBody) => methodBody.Depth() - } - } - } - - datatype Program = Program(mainMethod: Method) { - function method Depth() : nat { - 1 + match this { - case Program(mainMethod) => - mainMethod.Depth() - } - } - } } From fe168ae6e27a8a95c9099385b24ec0eee19a43d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Tue, 16 Aug 2022 14:25:53 -0700 Subject: [PATCH 006/105] debug: Add a simple printer for entity hierarchies --- src/Debug/Entities.dfy | 74 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 src/Debug/Entities.dfy diff --git a/src/Debug/Entities.dfy b/src/Debug/Entities.dfy new file mode 100644 index 00000000..5623ac83 --- /dev/null +++ b/src/Debug/Entities.dfy @@ -0,0 +1,74 @@ +include "../AST/Entities.dfy" +include "../Utils/Library.dfy" + +module {:options "-functionSyntax:4"} Bootstrap.Debug.Entities { + import Utils.Lib.Seq + import Utils.Lib.Str + import opened AST.Names + import opened AST.Entities + + datatype RegistryPrinter = RegistryPrinter(registry: Registry) { + static const INDENT := " "; + + static function Paragraph(title: string, indent: string): string { + indent + "- " + title + ": " + } + + static function Title(title: string, indent: string): string { + indent + "== " + title + " ==\n" + } + + static function Subtitle(title: string, indent: string): string { + indent + " - " + title + " -\n" + } + + static method DumpEntityHeader(ei: EntityInfo, indent: string) { + print Title(ei.name.ToString(), indent); + DumpEntityAttributes(ei.attrs, indent); + } + + static method DumpEntityAttributes(attrs: seq, indent: string) { + if attrs != [] { + print indent, "- Attributes: ", + Str.Join(" ", Seq.Map((at: Attribute) => at.ToString(), attrs)), + "\n"; + } + } + + method DumpEntity(e: Entity, indent: string) + requires e.ei.name in registry.entities + decreases registry.SuffixesOf(e.ei.name), 0 + { + registry.Decreases_SuffixesOfMany(e.ei); + DumpEntityHeader(e.ei, indent); + print Subtitle("Members", indent); + DumpEntities(e.ei.members, indent + INDENT); + } + + method DumpEntities(names: seq, indent: string) + decreases registry.SuffixesOfMany(names), 1 + { + for i := 0 to |names| { + var name := names[i]; + match registry.Find(name) + case None => + print indent, "!! Name not found:", name, "\n"; + case Some(e) => + registry.Decreases_SuffixesOf(names, name); + DumpEntity(e, indent); + print "\n"; + } + } + + method DumpEntitiesFlat() { + var names := registry.entities.Keys; + while |names| > 0 + invariant names <= registry.entities.Keys + { + var name :| name in names; + DumpEntity(registry.entities[name], indent := ""); + names := names - {name}; + } + } + } +} From 6f916496fa0800f691d8d6b4bf0eec1238d789b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Wed, 17 Aug 2022 10:16:46 -0700 Subject: [PATCH 007/105] ast: Improve verification performance of witnesses in Entities.dfy --- src/AST/Entities.dfy | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/AST/Entities.dfy b/src/AST/Entities.dfy index 09fb1838..00c4a088 100644 --- a/src/AST/Entities.dfy +++ b/src/AST/Entities.dfy @@ -69,10 +69,16 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities | EDefinition type EntityInfo = e: EntityInfo_ | e.Valid?() - witness EntityInfo(Anonymous, attrs := [], members := []) + witness EntityInfo_.EMPTY() datatype EntityInfo_ = EntityInfo(name: Name, nameonly attrs: seq, nameonly members: seq) { + static function EMPTY(): (ei: EntityInfo_) + ensures ei.Valid?() + { + EntityInfo(Anonymous, attrs := [], members := []) + } + ghost predicate Valid?() { forall nm <- members :: nm.ChildOf(name) } @@ -112,7 +118,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities type EntityMap = f | EntityMap?(f) witness e => e type Registry = r: Registry_ | r.Valid?() - witness Registry(map[]) + witness Registry_.EMPTY() datatype Registry_ = Registry(entities: map) // A collection of Dafny entities, index by name. // @@ -122,6 +128,12 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities // below, along with the two recursion lemmas `Decreases_SuffixesOf` and // `Decreases_SuffixesOfMany`. { + static function EMPTY(): (r: Registry_) + ensures r.Valid?() + { + Registry(map[]) + } + ghost predicate ValidNames?() { forall n <- entities :: entities[n].ei.name == n } From 7473049ca85c5dc31990af18f66ac85bfd9053c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Wed, 17 Aug 2022 10:17:58 -0700 Subject: [PATCH 008/105] ast: Improve verification performance of Registry.Valid?() --- src/AST/Entities.dfy | 48 ++++++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/src/AST/Entities.dfy b/src/AST/Entities.dfy index 00c4a088..066a9ef8 100644 --- a/src/AST/Entities.dfy +++ b/src/AST/Entities.dfy @@ -134,38 +134,46 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities Registry(map[]) } - ghost predicate ValidNames?() { - forall n <- entities :: entities[n].ei.name == n - } - - ghost predicate ValidParent??(n: Name) { - n == Anonymous || n.parent in entities + ghost predicate ValidName??(name: Name, entity: Entity) { + entity.ei.name == name } - ghost predicate ValidParents?() { - forall n <- entities :: ValidParent??(n) + ghost predicate ValidParent??(name: Name) { + name == Anonymous || name.parent in entities } ghost predicate ValidMembers??(ei: EntityInfo) { forall m <- ei.members :: m in entities } + ghost predicate ValidEntry??(name: Name, e: Entity) { + && ValidName??(name, e) + && ValidParent??(name) + && ValidMembers??(e.ei) + } + + ghost predicate ValidNames?() { + forall name <- entities :: ValidName??(name, entities[name]) + } + + ghost predicate ValidParents?() { + forall name <- entities :: ValidParent??(name) + } + ghost predicate ValidMembers?() { - forall n <- entities :: ValidMembers??(entities[n].ei) + forall name <- entities :: ValidMembers??(entities[name].ei) } ghost predicate Valid?() { - && ValidNames?() - && ValidParents?() - && ValidMembers?() + forall name <- entities :: ValidEntry??(name, entities[name]) } - ghost function {:opaque} SuffixesOf(name: Name): set { - set n <- entities | n.SuffixOf(name) + ghost function {:opaque} SuffixesOf(prefix: Name): set { + set name <- entities | name.SuffixOf(prefix) } - ghost function {:opaque} SuffixesOfMany(names: seq): set { - set n <- names, sf <- SuffixesOf(n) :: sf + ghost function {:opaque} SuffixesOfMany(prefixes: seq): set { + set prefix <- prefixes, name <- SuffixesOf(prefix) :: name } lemma Decreases_SuffixesOfMany(ei: EntityInfo) @@ -176,11 +184,11 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities reveal SuffixesOfMany(); assert SuffixesOfMany(ei.members) <= SuffixesOf(ei.name) by { - forall sf <- SuffixesOfMany(ei.members) - ensures sf in SuffixesOf(ei.name) + forall name <- SuffixesOfMany(ei.members) + ensures name in SuffixesOf(ei.name) { - var name: Name :| name in ei.members && sf in SuffixesOf(name); - Name.SuffixOf_Transitive(ei.name, name, sf); + var prefix: Name :| prefix in ei.members && name in SuffixesOf(prefix); + Name.SuffixOf_Transitive(ei.name, prefix, name); } } From 0c498f4fee91421d6537d26fbeeb49c51ebd336b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Wed, 17 Aug 2022 10:18:18 -0700 Subject: [PATCH 009/105] ast: Add a `Program` entity --- src/AST/Entities.dfy | 65 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 61 insertions(+), 4 deletions(-) diff --git a/src/AST/Entities.dfy b/src/AST/Entities.dfy index 066a9ef8..837b52a4 100644 --- a/src/AST/Entities.dfy +++ b/src/AST/Entities.dfy @@ -82,6 +82,12 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities ghost predicate Valid?() { forall nm <- members :: nm.ChildOf(name) } + + static function Mk(name: Name): EntityInfo + // Construct an `EntityInfo` instance with no attributes and no members. + { + EntityInfo(name, attrs := [], members := []) + } } datatype Entity = @@ -204,14 +210,65 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities reveal SuffixesOfMany(); } - function Find(n: Name) : Option { - if n in entities then Some(entities[n]) else None + predicate Contains(name: Name) { + name in entities + } + + function Get(name: Name): Entity + requires Contains(name) + { + entities[name] + } + + function Lookup(name: Name): Option { + if Contains(name) then Some(Get(name)) else None + } + + predicate HasKind(name: Name, kind: EntityKind) { + Contains(name) && Get(name).kind == kind + } + + function Add(name: Name, entity: Entity): Registry + requires Valid?() + requires !Contains(name) + requires entity.Module? + requires ValidEntry??(name, entity) + { + this.(entities := entities[name := entity]) } - function Map(f: EntityMap) : Registry + function Map(f: EntityMap): Registry requires Valid?() { - Registry(map n | n in entities :: f(entities[n])) + Registry(map name | name in entities :: f(entities[name])) + } + } + + type Program = p: Program_ | p.Valid?() witness Program.EMPTY() + datatype Program_ = + Program(r: Registry, + defaultModule: Name, + mainMethod: Option) + { + static function EMPTY(): (p: Program_) ensures p.Valid?() { + Program( + Registry.EMPTY().Add(Anonymous, Entity.Module(EntityInfo.Mk(Anonymous))), + defaultModule := Anonymous, + mainMethod := None + ) + } + + predicate ValidDefaultModule?() { + r.HasKind(defaultModule, EModule) + } + + predicate ValidMainMethod?() { + mainMethod.Some? ==> r.HasKind(mainMethod.value, EModule) + } + + predicate Valid?() { + && ValidDefaultModule?() + && ValidMainMethod?() } } } From fe4d45703d92289b5c9fdee431d1d8a1ac450b80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Wed, 17 Aug 2022 11:04:49 -0700 Subject: [PATCH 010/105] debug: Simplify traversal by leveraging well-formedness of registry --- src/AST/Entities.dfy | 18 ++++++++++-------- src/Debug/Entities.dfy | 28 +++++++++++++++------------- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src/AST/Entities.dfy b/src/AST/Entities.dfy index 837b52a4..349f1378 100644 --- a/src/AST/Entities.dfy +++ b/src/AST/Entities.dfy @@ -237,18 +237,16 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities this.(entities := entities[name := entity]) } - function Map(f: EntityMap): Registry - requires Valid?() - { + function Map(f: EntityMap): Registry requires Valid?() { Registry(map name | name in entities :: f(entities[name])) } } type Program = p: Program_ | p.Valid?() witness Program.EMPTY() datatype Program_ = - Program(r: Registry, - defaultModule: Name, - mainMethod: Option) + Program(registry: Registry, + nameonly defaultModule: Name, + nameonly mainMethod: Option) { static function EMPTY(): (p: Program_) ensures p.Valid?() { Program( @@ -259,16 +257,20 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities } predicate ValidDefaultModule?() { - r.HasKind(defaultModule, EModule) + registry.HasKind(defaultModule, EModule) } predicate ValidMainMethod?() { - mainMethod.Some? ==> r.HasKind(mainMethod.value, EModule) + mainMethod.Some? ==> registry.HasKind(mainMethod.value, EModule) } predicate Valid?() { && ValidDefaultModule?() && ValidMainMethod?() } + + function DefaultModule(): Entity requires Valid?() { + registry.Get(defaultModule) + } } } diff --git a/src/Debug/Entities.dfy b/src/Debug/Entities.dfy index 5623ac83..8571b4fc 100644 --- a/src/Debug/Entities.dfy +++ b/src/Debug/Entities.dfy @@ -35,27 +35,25 @@ module {:options "-functionSyntax:4"} Bootstrap.Debug.Entities { } } - method DumpEntity(e: Entity, indent: string) - requires e.ei.name in registry.entities - decreases registry.SuffixesOf(e.ei.name), 0 + method DumpEntity(name: Name, indent: string) + requires registry.Contains(name) + decreases registry.SuffixesOf(name), 0 { - registry.Decreases_SuffixesOfMany(e.ei); - DumpEntityHeader(e.ei, indent); + var entity := registry.Get(name); + DumpEntityHeader(entity.ei, indent); print Subtitle("Members", indent); - DumpEntities(e.ei.members, indent + INDENT); + registry.Decreases_SuffixesOfMany(entity.ei); + DumpEntities(entity.ei.members, indent + INDENT); } method DumpEntities(names: seq, indent: string) + requires forall name <- names :: registry.Contains(name) decreases registry.SuffixesOfMany(names), 1 { for i := 0 to |names| { var name := names[i]; - match registry.Find(name) - case None => - print indent, "!! Name not found:", name, "\n"; - case Some(e) => - registry.Decreases_SuffixesOf(names, name); - DumpEntity(e, indent); + registry.Decreases_SuffixesOf(names, name); + DumpEntity(name, indent); print "\n"; } } @@ -66,9 +64,13 @@ module {:options "-functionSyntax:4"} Bootstrap.Debug.Entities { invariant names <= registry.entities.Keys { var name :| name in names; - DumpEntity(registry.entities[name], indent := ""); + DumpEntity(name, indent := ""); names := names - {name}; } } } + + method DumpProgram(p: Program) { + RegistryPrinter(p.registry).DumpEntity(p.defaultModule, indent := ""); + } } From 813caba9e96008fe88352e019365d48bf9802e08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Fri, 19 Aug 2022 09:18:06 -0700 Subject: [PATCH 011/105] lib: Check in sorting routine --- src/Utils/Lib.Sort.dfy | 444 +++++++++++++++++++++++++++++++++++++++++ src/Utils/Library.dfy | 394 +++++++++++++++++++++++++++++++++++- 2 files changed, 831 insertions(+), 7 deletions(-) create mode 100644 src/Utils/Lib.Sort.dfy diff --git a/src/Utils/Lib.Sort.dfy b/src/Utils/Lib.Sort.dfy new file mode 100644 index 00000000..c5669675 --- /dev/null +++ b/src/Utils/Lib.Sort.dfy @@ -0,0 +1,444 @@ +include "Library.dfy" + +module Utils.Lib.ArraySort { + import opened C = Sort.Comparison + import Set + + trait Sorter { + ghost const C := Comparison(Cmp) + + function method Cmp(t0: T, t1: T): Cmp + + twostate predicate Identical(arr: array, lo: int, hi: int) + requires 0 <= lo <= hi <= arr.Length + reads arr + { + arr[lo..hi] == old(arr[lo..hi]) + } + + twostate lemma IdenticalSplit(arr: array, lo: int, mid: int, hi: int) + requires 0 <= lo <= mid <= hi <= arr.Length + requires Identical(arr, lo, hi) + ensures Identical(arr, lo, mid) + ensures Identical(arr, mid, hi) + { + assert arr[lo..mid] == arr[lo..hi][..mid-lo]; + } + + twostate predicate Shuffled(arr: array, lo: int, hi: int) + requires 0 <= lo <= hi <= arr.Length + reads arr + { + multiset(arr[lo..hi]) == old(multiset(arr[lo..hi])) + } + + twostate lemma IdenticalShuffled(arr: array, lo: int, hi: int) + requires 0 <= lo <= hi <= arr.Length + requires Identical(arr, lo, hi) + ensures Shuffled(arr, lo, hi) + {} + + twostate predicate SameElements(arr: array, lo: int, hi: int) + reads arr + requires 0 <= lo <= hi <= arr.Length + { + && Identical(arr, 0, lo) + && Shuffled(arr, lo, hi) + && Identical(arr, hi, arr.Length) + } + + twostate lemma SameElementsExtend(arr: array, lo: int, lo': int, hi': int, hi: int) + requires 0 <= lo <= lo' <= hi' <= hi <= arr.Length + requires SameElements(arr, lo', hi') + ensures SameElements(arr, lo, hi) + { + assert SameElements(arr, lo', hi'); + assert Identical(arr, 0, lo') && Shuffled(arr, lo', hi') && Identical(arr, hi', arr.Length); + IdenticalSplit(arr, 0, lo, lo'); IdenticalSplit(arr, hi', hi, arr.Length); + assert && Identical(arr, 0, lo) && Identical(arr, lo, lo') + && Shuffled(arr, lo', hi') + && Identical(arr, hi', hi) && Identical(arr, hi, arr.Length); + IdenticalShuffled(arr, lo, lo'); IdenticalShuffled(arr, hi', hi); + assert && Identical(arr, 0, lo) && Shuffled(arr, lo, lo') + && Shuffled(arr, lo', hi') + && Shuffled(arr, hi', hi) && Identical(arr, hi, arr.Length); + ShuffledConcat(arr, lo, lo', hi'); ShuffledConcat(arr, lo, hi', hi); + assert Identical(arr, 0, lo) && Shuffled(arr, lo, hi) && Identical(arr, hi, arr.Length); + assert SameElements(arr, lo, hi); + } + + twostate lemma SetOfSliceShuffled(arr: array, lo: int, hi: int) + requires 0 <= lo <= hi <= arr.Length + requires Shuffled(arr, lo, hi) + ensures Set.OfSlice(arr, lo, hi) == old(Set.OfSlice(arr, lo, hi)) + { + calc { + old(Set.OfSlice(arr, lo, hi)); + set x <- old(arr[lo..hi]); + set x <- old(multiset(arr[lo..hi])); + set x <- multiset(arr[lo..hi]); + set x <- arr[lo..hi]; + Set.OfSlice(arr, lo, hi); + } + } + + predicate Sortable(arr: array, lo: int, hi: int) + requires 0 <= lo <= hi <= arr.Length + reads arr + { + C.Valid?(Set.OfSlice(arr, lo, hi)) + } + + lemma Sortable_Slice(arr: array, lo: int, hi: int, lo': int, hi': int) + requires 0 <= lo <= lo' <= hi' <= hi <= arr.Length + requires Sortable(arr, lo, hi) + ensures Sortable(arr, lo', hi') + {} + + twostate lemma SortableShuffled(arr: array, lo: int, hi: int) + requires 0 <= lo <= hi <= arr.Length + requires old(Sortable(arr, lo, hi)) + requires Shuffled(arr, lo, hi) + ensures Sortable(arr, lo, hi) + { + SetOfSliceShuffled(arr, lo, hi); + } + + twostate lemma SameElementsShuffled(arr: array, lo: int, hi: int) + requires 0 <= lo <= hi <= arr.Length + requires old(Sortable(arr, lo, hi)) + requires SameElements(arr, lo, hi) + ensures Shuffled(arr, lo, hi) + { + SetOfSliceShuffled(arr, lo, hi); + } + + twostate lemma SortableSameElements(arr: array, lo: int, hi: int) + requires 0 <= lo <= hi <= arr.Length + requires old(Sortable(arr, lo, hi)) + requires SameElements(arr, lo, hi) + ensures Sortable(arr, lo, hi) + { + SetOfSliceShuffled(arr, lo, hi); + } + + twostate lemma ShuffledConcat(arr: array, lo: int, mid: int, hi: int) + requires 0 <= lo <= mid <= hi <= arr.Length + requires Shuffled(arr, lo, mid) + requires Shuffled(arr, mid, hi) + ensures Shuffled(arr, lo, hi) + { + calc { + old(multiset(arr[lo..hi])); + { assert old(arr[lo..hi] == arr[lo..mid] + arr[mid..hi]); } + old(multiset(arr[lo..mid] + arr[mid..hi])); + old(multiset(arr[lo..mid])) + old(multiset(arr[mid..hi])); + multiset(arr[lo..mid]) + multiset(arr[mid..hi]); + { assert arr[lo..hi] == arr[lo..mid] + arr[mid..hi]; } + multiset(arr[lo..hi]); + } + } + + predicate Sorted(arr: array, lo: int, hi: int) + reads arr + requires 0 <= lo <= hi <= arr.Length + { + C.Sorted(arr[lo..hi]) + } + + lemma SortedConcat(arr: array, lo: int, mid: int, hi: int) + requires 0 <= lo <= mid <= hi <= arr.Length + requires Sorted(arr, lo, mid) + requires Sorted(arr, mid, hi) + requires forall i, j | lo <= i < mid <= j < hi :: Cmp(arr[i], arr[j]).Le? + ensures Sorted(arr, lo, hi) + { + assert arr[lo..mid] + arr[mid..hi] == arr[lo..hi]; + C.SortedConcat(arr[lo..mid], arr[mid..hi]); + } + + twostate lemma SortedIdentical(arr: array, lo: int, hi: int) + requires 0 <= lo <= hi <= arr.Length + requires old(Sorted(arr, lo, hi)) + requires Identical(arr, lo, hi) + ensures Sorted(arr, lo, hi) + { + assert arr[lo..hi] == old(arr[lo..hi]); + forall i, j | lo <= i < j < hi ensures Cmp(arr[i], arr[j]).Le? { + assert arr[i] == arr[lo..hi][i - lo]; + assert arr[j] == arr[lo..hi][j - lo]; + } + } + + function method MedianOfMedians(arr: array, lo: int, hi: int): (t: T) + requires 0 <= lo < hi <= arr.Length + reads arr + ensures t in arr[lo..hi] + { + arr[lo] // TODO + } + + lemma StripedInit(sq: seq, pivot: T, lo: int, hi: int) + requires 0 <= lo <= hi <= |sq| + ensures C.Striped(sq, pivot, lo, lo, lo, hi, hi) + { + reveal C.Striped(); + } + + lemma StripedNonEmpty(sq: seq, pivot: T, lo: int, left: int, mid: int, right: int, hi: int) + requires 0 <= lo <= left <= mid <= right <= hi <= |sq| + requires C.Striped(sq, pivot, lo, left, mid, right, hi) + requires C.Valid?(set x <- sq[lo..hi]) + requires pivot in sq[lo..hi] + ensures left < right + { + var idx :| lo <= idx < hi && sq[idx] == pivot; + C.AlwaysReflexive(pivot); + reveal C.Striped(); + } + + twostate lemma StripedSameElements(arr: array, pivot: T, lo: int, left: int, right: int, hi: int) + requires 0 <= lo <= left <= right <= hi <= |arr[..]| + requires Shuffled(arr, lo, left) + requires Identical(arr, left, right) + requires Shuffled(arr, right, hi) + requires old(C.Striped(arr[..], pivot, lo, left, right, right, hi)) + ensures C.Striped(arr[..], pivot, lo, left, right, right, hi) + { + reveal C.Striped(); + forall i | lo <= i < left ensures Cmp(arr[..][i], pivot).Lt? { + assert arr[..][i] == arr[lo..left][i - lo]; + assert arr[..][i] in multiset(arr[lo..left]); + } + forall i | left <= i < right ensures Cmp(arr[..][i], pivot).Eq? { + assert arr[..][i] == arr[left..right][i - left]; + } + forall i | right <= i < hi ensures Cmp(arr[..][i], pivot).Gt? { + assert arr[..][i] == arr[right..hi][i - right]; + assert arr[..][i] in multiset(arr[right..hi]); + } + } + + // ensures Sorted(arr, left, right) + method Swap(arr: array, ghost lo: int, i: int, j: int, ghost hi: int) + requires 0 <= lo <= i <= j < hi <= arr.Length + modifies arr + ensures SameElements(arr, lo, hi) + ensures arr[..] == old(arr[..][i := arr[j]][j := arr[i]]) + { + arr[i], arr[j] := arr[j], arr[i]; + } + + method SwapLt( + arr: array, pivot: T, + lo: int, left: int, mid: int, right: int, hi: int + ) + requires 0 <= lo <= left <= mid < right <= hi <= arr.Length + requires C.Striped(arr[..], pivot, lo, left, mid, right, hi) + requires Cmp(arr[mid], pivot).Lt? + modifies arr + ensures SameElements(arr, lo, hi) + ensures arr[..] == old(arr[..][left := arr[mid]][mid := arr[left]]) + ensures C.Striped(arr[..], pivot, lo, left + 1, mid + 1, right, hi) + { + reveal C.Striped(); + Swap(arr, lo, left, mid, hi); + } + + method SwapGt( + arr: array, pivot: T, + lo: int, left: int, mid: int, right: int, hi: int + ) + requires 0 <= lo <= left <= mid < right <= hi <= arr.Length + requires C.Striped(arr[..], pivot, lo, left, mid, right, hi) + requires Cmp(arr[mid], pivot).Gt? + modifies arr + ensures SameElements(arr, lo, hi) + ensures arr[..] == old(arr[..][mid := arr[right - 1]][right - 1 := arr[mid]]) + ensures C.Striped(arr[..], pivot, lo, left, mid, right - 1, hi) + { + reveal C.Striped(); + Swap(arr, lo, mid, right - 1, hi); + } + + lemma SwapEq( + arr: array, pivot: T, + lo: int, left: int, mid: int, right: int, hi: int + ) + requires 0 <= lo <= left <= mid < right <= hi <= arr.Length + requires C.Striped(arr[..], pivot, lo, left, mid, right, hi) + requires Cmp(arr[mid], pivot).Eq? + ensures SameElements(arr, lo, hi) + ensures C.Striped(arr[..], pivot, lo, left, mid + 1, right, hi) + { + reveal C.Striped(); + } + + method DutchFlag(arr: array, pivot: T, lo: int, hi: int) returns (left: int, right: int) + requires 0 <= lo < hi <= arr.Length + requires pivot in multiset(arr[lo..hi]) + modifies arr + ensures SameElements(arr, lo, hi) + ensures lo <= left <= right <= hi + ensures C.Striped(arr[..], pivot, lo, left, right, right, hi) + { + left, right := lo, hi; + var mid := lo; + StripedInit(arr[..], pivot, lo, hi); + + while mid < right + invariant pivot in multiset(arr[lo..hi]) + invariant lo <= left <= mid <= right <= hi + invariant SameElements(arr, lo, hi) + invariant C.Striped(arr[..], pivot, lo, left, mid, right, hi) + { + reveal C.Striped(); + match Cmp(arr[mid], pivot) { + case Lt => + SwapLt(arr, pivot, lo, left, mid, right, hi); + left := left + 1; + mid := mid + 1; + case Gt => + SwapGt(arr, pivot, lo, left, mid, right, hi); + right := right - 1; + case Eq => + mid := mid + 1; + } + } + } + + lemma SortedStripedMiddle(arr: array, pivot: T, lo: int, left: int, right: int, hi: int) + requires 0 <= lo <= left < right <= hi <= arr.Length + requires Sortable(arr, lo, hi) + requires pivot in multiset(arr[lo..hi]) + requires C.Striped(arr[..], pivot, lo, left, right, right, hi) + ensures Sorted(arr, left, right) + { + reveal C.Striped(); + forall i, j | left <= i < j < right ensures Cmp(arr[i], arr[j]).Le? { + var idx :| lo <= idx < hi && arr[idx] == pivot; + assert Cmp(arr[i], pivot) == Eq; + assert C.Complete??(arr[j], pivot); + assert C.Transitive??(arr[i], pivot, arr[j]); + } + } + + lemma SortedDutchFlag(arr: array, pivot: T, lo: int, left: int, right: int, hi: int) + requires 0 <= lo <= left < right <= hi <= arr.Length + requires Sortable(arr, lo, hi) + requires pivot in multiset(arr[lo..hi]) + requires Sorted(arr, lo, left) + requires Sorted(arr, right, hi) + requires C.Striped(arr[..], pivot, lo, left, right, right, hi) + ensures Sorted(arr, lo, hi) + { + reveal C.Striped(); + forall i, j | lo <= i < left <= j < right ensures Cmp(arr[i], arr[j]).Le? { + var idx :| lo <= idx < hi && arr[idx] == pivot; + assert Cmp(arr[i], pivot).Le?; + assert Cmp(arr[j], pivot).Eq?; + assert C.Complete??(arr[j], pivot); + assert C.Transitive??(arr[i], pivot, arr[j]); + } + SortedStripedMiddle(arr, pivot, lo, left, right, hi); + SortedConcat(arr, lo, left, right); + forall i, j | lo <= i < right <= j < hi ensures Cmp(arr[i], arr[j]).Le? { + assert Cmp(arr[i], pivot).Le?; + assert Cmp(arr[j], pivot).Gt?; + assert C.Complete??(arr[i], pivot); + assert C.Complete??(arr[j], pivot); + assert C.Transitive??(arr[i], pivot, arr[j]); + } + SortedConcat(arr, lo, right, hi); + } + + method QuickSort(arr: array, lo: int := 0, hi: int := arr.Length) + requires 0 <= lo <= hi <= arr.Length + requires Sortable(arr, lo, hi) + decreases hi - lo + modifies arr + ensures Sortable(arr, lo, hi) + ensures Sorted(arr, lo, hi) + ensures SameElements(arr, lo, hi) + { + if hi - lo > 1 { + var pivot := MedianOfMedians(arr, lo, hi); + var left, right := DutchFlag(arr, pivot, lo, hi); + + label left: + SortableSameElements(arr, lo, hi); + Sortable_Slice(arr, lo, hi, lo, left); + StripedNonEmpty(arr[..], pivot, lo, left, right, right, hi); + QuickSort(arr, lo, left); + + label right: + SameElementsExtend@left(arr, lo, lo, left, hi); + SortableSameElements(arr, lo, hi); + Sortable_Slice(arr, lo, hi, right, hi); + IdenticalSplit@left(arr, left, hi, arr.Length); + IdenticalSplit@left(arr, left, right, hi); + IdenticalShuffled@left(arr, right, hi); + StripedSameElements@left(arr, pivot, lo, left, right, hi); + QuickSort(arr, right, hi); + + SameElementsExtend@right(arr, lo, right, hi, hi); + SortableSameElements(arr, lo, hi); + IdenticalSplit@right(arr, lo, left, right); + SortedIdentical@right(arr, lo, left); + StripedSameElements@right(arr, pivot, lo, left, right, hi); + SortedDutchFlag(arr, pivot, lo, left, right, hi); + } + } + } + + class PredicateSorter extends Sorter { + const cmp: (T, T) -> Cmp + + constructor(cmp: (T, T) -> Cmp) ensures this.cmp == cmp { + this.cmp := cmp; + } + + function method Cmp(t0: T, t1: T): Cmp { + cmp(t0, t1) + } + } +} + +module Utils.Lib.SetSort { + import opened C = Sort.Comparison + import Array + import ArraySort + + // function Sort(st: set, cmp: Comparison): (sq: seq) + // requires cmp.Valid?(st) + // ensures st == set x <- sq + // ensures cmp.Sorted(sq) + // { + // [] + // } by method { + // sq := QuickSort(st, cmp); + // { + + method QuickSort(st: set, cmp: Comparison) returns (sq: seq) + requires cmp.Valid?(st) + ensures st == set x <- sq + ensures cmp.Sorted(sq) + { + var arr := Array.OfSet(st); + assert Array.Set.OfArray(arr) == st; + var sorter := new ArraySort.PredicateSorter(cmp.cmp); + label unsorted: + sorter.QuickSort(arr); + assert arr[0..arr.Length] == arr[..]; + assert old@unsorted(arr[0..arr.Length]) == old@unsorted(arr[..]); + calc { + set x <- arr[..]; + set x <- multiset(arr[..]); + set x <- old@unsorted(multiset(arr[..])); + set x <- multiset(st); + set x <- st; + st; + } + return arr[..]; + } +} diff --git a/src/Utils/Library.dfy b/src/Utils/Library.dfy index 454f658d..3ea5ccf6 100644 --- a/src/Utils/Library.dfy +++ b/src/Utils/Library.dfy @@ -1,5 +1,4 @@ -module Utils.Lib { -module ControlFlow { +module Utils.Lib.ControlFlow { function method Unreachable() : A requires false { @@ -7,7 +6,7 @@ module ControlFlow { } } -module Debug { +module Utils.Lib.Debug { class {:compile false} Debugger { static method {:extern "System.Diagnostics.Debugger", "Break"} Break() {} } @@ -20,7 +19,7 @@ module Debug { } } -module Datatypes { +module Utils.Lib.Datatypes { datatype Option<+T> = | Some(value: T) | None { function method UnwrapOr(default: T) : T { @@ -93,7 +92,7 @@ module Datatypes { } } -module Seq { +module Utils.Lib.Seq { // FIXME why not use a comprehension directly? function method {:opaque} Map(f: T ~> Q, ts: seq) : (qs: seq) reads f.reads @@ -276,7 +275,24 @@ module Seq { } } -module Str { +module Utils.Lib.Outcome.OfSeq { // FIXME rename to Seq + import Seq + import opened Datatypes + + function method Combine(so: seq>): (os: Outcome>) + ensures os.Pass? ==> forall o | o in so :: o.Pass? + ensures os.Pass? <== forall o | o in so :: o.Pass? + { + var fails := Seq.Filter(so, (x: Outcome) => x.Fail?); + if |fails| == 0 then + Pass + else + assert fails[0] in so; + Fail(Seq.Map((x: Outcome) requires x.Fail? => x.error, fails)) + } +} + +module Utils.Lib.Str { module Private { function method digits(n: int, base: int): (digits: seq) requires base > 1 @@ -355,7 +371,203 @@ module Private { } } -module Math { +module Utils.Lib.Set { + function method Map(ts: set, f: T ~> T'): set + reads set t <- ts, o <- f.reads(t) :: o + requires forall t <- ts :: f.requires(t) + { + set t <- ts :: f(t) + } + + function method OfSeq(sq: seq): set { + set x <- sq + } + + function method OfSlice(arr: array, lo: int, hi: int): set + requires 0 <= lo <= hi <= arr.Length + reads arr + { + OfSeq(arr[lo..hi]) + } + + function method OfArray(arr: array): set + reads arr + { + OfSlice(arr, 0, arr.Length) + } +} + +module Utils.Lib.Multiset { + function method OfSlice(arr: array, lo: int, hi: int): multiset + requires 0 <= lo <= hi <= arr.Length + reads arr + { + multiset(arr[lo..hi]) + } + + function method OfArray(arr: array): multiset + reads arr + { + OfSlice(arr, 0, arr.Length) + } +} + +module Utils.Lib.Array { + import Set + import Multiset + + method OfSet(st: set) returns (arr: array) + ensures fresh(arr) + ensures Set.OfArray(arr) == st + ensures Multiset.OfArray(arr) == multiset(st) + { + if |st| == 0 { + arr := new T[|st|]; + } else { + var s := st; + var t0 :| t0 in s; + arr := new T[|st|](_ => t0); + for i := 0 to |s| + invariant |s| == |st| - i + invariant Set.OfSlice(arr, 0, i) + s == st + invariant Multiset.OfSlice(arr, 0, i) + multiset(s) == multiset(st) + { + var t :| t in s; + arr[i] := t; + s := s - {t}; + assert Set.OfSlice(arr, 0, i + 1) == Set.OfSlice(arr, 0, i) + {t}; + } + } + } +} + +module Utils.Lib.Int.Comparison { + import C = Sort.Comparison + + function method Cmp(i0: int, i1: int): C.Cmp { + if i0 < i1 then C.Lt + else if i0 > i1 then C.Gt + else C.Eq + } + + const Comparison := C.Comparison(Cmp); + + lemma {:induction ints} Total(ints: set) + ensures Comparison.Total?(ints) + {} +} + +module Utils.Lib.Char.Comparison { + import C = Sort.Comparison + + function method Cmp(i0: char, i1: char): C.Cmp { + if i0 < i1 then C.Lt + else if i0 > i1 then C.Gt + else C.Eq + } + + const Comparison := C.Comparison(Cmp); + + lemma {:induction chars} Total(chars: set) + ensures Comparison.Total?(chars) + {} +} + +module Utils.Lib.Seq.Comparison { + import Set + import C = Sort.Comparison + + function Flatten(tss: set>): set { + set ts <- tss, t <- ts :: t + } + + datatype SeqComparison = SeqComparison(cmp: C.Comparison) { + function method Cmp(s0: seq, s1: seq): C.Cmp { + if s0 == [] && s1 == [] then C.Eq + else if s0 == [] then C.Lt + else if s1 == [] then C.Gt + else + match cmp.Compare(s0[0], s1[0]) + case Eq => Cmp(s0[1..], s1[1..]) + case other => other + } + + const Comparison := C.Comparison(Cmp); + + lemma {:induction false} Complete1(s0: seq, s1: seq) + requires cmp.Total?(Set.OfSeq(s0) + Set.OfSeq(s1)) + ensures Comparison.Complete??(s0, s1) + { + if s0 != [] && s1 != [] { + assert s0[0] in s0 && s1[0] in s1; + assert s0[0] in Set.OfSeq(s0) && s1[0] in Set.OfSeq(s1); + Complete1(s0[1..], s1[1..]); + } + } + + lemma {:induction false} Antisymmetric1(s0: seq, s1: seq) + requires cmp.Total?(Set.OfSeq(s0) + Set.OfSeq(s1)) + ensures Comparison.Antisymmetric??(s0, s1) + { + // forall s0, s1 | s0 in tss && s1 in tss ensures Comparison.Complete??(s0, s1) { + if s0 != [] && s1 != [] { + assert s0[0] in s0 && s1[0] in s1; + assert s0[0] in Set.OfSeq(s0) && s1[0] in Set.OfSeq(s1); + assert s0 == [s0[0]] + s0[1..] && s1 == [s1[0]] + s1[1..]; + Antisymmetric1(s0[1..], s1[1..]); + } + } + + lemma {:induction false} Transitive1(s0: seq, s1: seq, s2: seq) + requires cmp.Total?(Set.OfSeq(s0) + Set.OfSeq(s1) + Set.OfSeq(s2)) + ensures Comparison.Transitive??(s0, s1, s2) + { + if s0 != [] && s1 != [] && s1 != [] && Comparison.Compare(s0, s1).Le? && Comparison.Compare(s1, s2).Le? { + assert s0[0] in s0 && s1[0] in s1 && s2[0] in s2; + assert s0[0] in Set.OfSeq(s0) && s1[0] in Set.OfSeq(s1) && s2[0] in Set.OfSeq(s2); + assert s0 == [s0[0]] + s0[1..] && s1 == [s1[0]] + s1[1..] && s2 == [s2[0]] + s2[1..]; + assert cmp.Compare(s0[0], s1[0]).Le?; + assert cmp.Compare(s1[0], s2[0]).Le?; + assert cmp.Transitive??(s0[0], s1[0], s2[0]); + assert cmp.Compare(s0[0], s2[0]).Le?; + Transitive1(s0[1..], s1[1..], s2[1..]); + } + } + + lemma {:induction false} Total(tss: set>) + requires cmp.Total?(Flatten(tss)) + ensures Comparison.Total?(tss) + { + forall s0, s1 | s0 in tss && s1 in tss ensures Comparison.Complete??(s0, s1) { + Complete1(s0, s1); + } + forall s0, s1 | s0 in tss && s1 in tss ensures Comparison.Antisymmetric??(s0, s1) { + Antisymmetric1(s0, s1); + } + forall s0, s1, s2 | s0 in tss && s1 in tss && s2 in tss ensures Comparison.Transitive??(s0, s1, s2) { + Transitive1(s0, s1, s2); + } + } + } +} + +module Utils.Lib.Str.Comparison { + import C = Sort.Comparison + import SeqCmp = Seq.Comparison + import CharCmp = Char.Comparison + + const StrComparison := SeqCmp.SeqComparison(CharCmp.Comparison); + const Comparison := StrComparison.Comparison; + + lemma {:induction false} Total(strs: set) + ensures Comparison.Total?(strs) + { + CharCmp.Total(SeqCmp.Flatten(strs)); + StrComparison.Total(strs); + } +} + +module Utils.Lib.Math { function method {:opaque} Max(x: int, y: int) : (m: int) ensures x <= m ensures y <= m @@ -369,4 +581,172 @@ module Math { else x * IntPow(x, n - 1) } } + +module Utils.Lib.Sort.Comparison { + import opened Datatypes + + datatype Cmp = Lt | Eq | Gt { + function method Flip(): Cmp { + match this + case Lt => Gt + case Eq => Eq + case Gt => Lt + } + + const Le? := this != Gt + const Ge? := this != Lt + + static function method ComputeTransitivity(c0: Cmp, c1: Cmp): Option { + match (c0, c1) + case (Lt, Lt) => Some(Lt) + case (Lt, Eq) => Some(Lt) + case (Lt, Gt) => None + case (Eq, Lt) => Some(Lt) + case (Eq, Eq) => Some(Eq) + case (Eq, Gt) => Some(Gt) + case (Gt, Lt) => None + case (Gt, Eq) => Some(Gt) + case (Gt, Gt) => Some(Gt) + } + } + + datatype Comparison = Comparison(cmp: (T, T) -> Cmp) { + function method Compare(t0: T, t1: T): Cmp { + cmp(t0, t1) + } + + predicate Complete??(t0: T, t1: T) { + cmp(t0, t1) == cmp(t1, t0).Flip() + } + + predicate Antisymmetric??(t0: T, t1: T) { + cmp(t0, t1) == Eq ==> t0 == t1 + } + + predicate Transitive??(t0: T, t1: T, t2: T) { + cmp(t0, t1).Le? && cmp(t1, t2).Le? ==> cmp(t0, t2).Le? + } + + predicate Reflexive??(t0: T) { + cmp(t0, t0) == Eq + } + + lemma AlwaysReflexive(t0: T) + requires Complete??(t0, t0) + ensures Reflexive??(t0) + {} + + lemma PreciselyTransitive'(t0: T, t1: T, t2: T) + requires Complete??(t0, t1) && Complete??(t0, t2) && Complete??(t1, t2) + requires Antisymmetric??(t0, t1) && Antisymmetric??(t0, t2) && Antisymmetric??(t1, t2) + requires Transitive??(t0, t1, t2) && Transitive??(t1, t2, t0) + requires cmp(t0, t1).Le? && cmp(t1, t2).Le? + ensures Cmp.ComputeTransitivity(cmp(t0, t1), cmp(t1, t2)) == Some(cmp(t0, t2)) + {} + + lemma PreciselyTransitive(t0: T, t1: T, t2: T) + requires Reflexive??(t0) && Reflexive??(t1) && Reflexive??(t2) + requires Complete??(t0, t1) && Complete??(t0, t2) && Complete??(t1, t2) + requires Antisymmetric??(t0, t1) && Antisymmetric??(t0, t2) && Antisymmetric??(t1, t2) + requires Transitive??(t0, t1, t2) && Transitive??(t1, t2, t0) + requires Transitive??(t2, t1, t0) && Transitive??(t1, t0, t2) + ensures match Cmp.ComputeTransitivity(cmp(t0, t1), cmp(t1, t2)) + case Some(c12) => c12 == cmp(t0, t2) + case None => true + { + match (cmp(t0, t1), cmp(t1, t2)) + case (Lt, Lt) | (Lt, Eq) | (Eq, Lt) | (Eq, Eq) => + PreciselyTransitive'(t0, t1, t2); + case (Eq, Gt) | (Gt, Eq) | (Gt, Gt) => + PreciselyTransitive'(t2, t1, t0); + case (Lt, Gt) | (Gt, Lt) => + } + + predicate Complete?(ts: set) { + forall t0, t1 | t0 in ts && t1 in ts :: Complete??(t0, t1) + } + + predicate Antisymmetric?(ts: set) { + forall t0, t1 | t0 in ts && t1 in ts :: Antisymmetric??(t0, t1) + } + + predicate Transitive?(ts: set) { + forall t0, t1, t2 | t0 in ts && t1 in ts && t2 in ts :: Transitive??(t0, t1, t2) + } + + predicate Valid?(ts: set) { + Complete?(ts) && /* Antisymmetric?(ts) && */ Transitive?(ts) + } + + predicate Total?(ts: set) { + Complete?(ts) && Antisymmetric?(ts) && Transitive?(ts) + } + + predicate Sorted(sq: seq) { + forall i, j | 0 <= i < j < |sq| :: cmp(sq[i], sq[j]).Le? + } + + lemma SortedConcat(sq0: seq, sq1: seq) + requires Sorted(sq0) + requires Sorted(sq1) + requires forall i, j | 0 <= i < |sq0| && 0 <= j < |sq1| :: cmp(sq0[i], sq1[j]).Le? + ensures Sorted(sq0 + sq1) + {} + + // lemma SortedUnique(sq0: seq, sq1: seq) + // requires Sorted(sq0) + // requires Sorted(sq1) + // requires multiset(sq0) == multiset(sq1) + // requires Antisymmetric?(set x <- sq0) + // ensures sq0 == sq1 + // { + // calc { set x <- sq0; set x <- multiset(sq0); set x <- multiset(sq1); set x <- sq1; } + // } + + predicate {:opaque} Striped(sq: seq, pivot: T, lo: int, left: int, mid: int, right: int, hi: int) + requires 0 <= lo <= left <= mid <= right <= hi <= |sq| + { + && (forall i | lo <= i < left :: cmp(sq[i], pivot).Lt?) + && (forall i | left <= i < mid :: cmp(sq[i], pivot).Eq?) + && (forall i | right <= i < hi :: cmp(sq[i], pivot).Gt?) + } + } +} + +module Utils.Lib.Sort.DerivedComparison { + import C = Comparison + import Set + + datatype DerivedComparison = DerivedComparison(cmp: C.Comparison, fn: T -> T') { + const Comparison := C.Comparison((t0, t1) => cmp.Compare(fn(t0), fn(t1))); + + lemma Valid(ts: set) + requires cmp.Valid?(Set.Map(ts, fn)) + ensures Comparison.Valid?(ts) + { + forall s0, s1 | s0 in ts && s1 in ts ensures Comparison.Complete??(s0, s1) { + assert cmp.Complete??(fn(s0), fn(s1)); + } + forall s0, s1, s2 | s0 in ts && s1 in ts && s2 in ts ensures Comparison.Transitive??(s0, s1, s2) { + assert cmp.Transitive??(fn(s0), fn(s1), fn(s2)); + } + } + + lemma Total(ts: set) + requires cmp.Total?(Set.Map(ts, fn)) + requires forall i, j :: fn(i) == fn(j) ==> i == j + ensures Comparison.Total?(ts) + { + Valid(ts); + forall s0, s1 | s0 in ts && s1 in ts ensures Comparison.Antisymmetric??(s0, s1) { + calc ==> { + Comparison.cmp(s0, s1).Eq?; + cmp.cmp(fn(s0), fn(s1)).Eq?; + { assert cmp.Antisymmetric??(fn(s0), fn(s1)); } + fn(s0) == fn(s1); + s0 == s1; + } + } + } + } } From ebdc189565829c8a6c126edcf5277bd778ed16ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Fri, 19 Aug 2022 12:20:54 -0700 Subject: [PATCH 012/105] lib: Add Seq.Flatten --- src/Utils/Library.dfy | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Utils/Library.dfy b/src/Utils/Library.dfy index 3ea5ccf6..b6f4252b 100644 --- a/src/Utils/Library.dfy +++ b/src/Utils/Library.dfy @@ -152,6 +152,11 @@ module Utils.Lib.Seq { if ts == [] then a0 else f(FoldL(f, a0, ts[1..]), ts[0]) } + function method Flatten(tss: seq>): seq { // TODO specify + if tss == [] then [] + else tss[0] + Flatten(tss[1..]) + } + // TODO: Why not use forall directly? function method {:opaque} All(P: T ~> bool, ts: seq) : (b: bool) reads P.reads @@ -280,8 +285,7 @@ module Utils.Lib.Outcome.OfSeq { // FIXME rename to Seq import opened Datatypes function method Combine(so: seq>): (os: Outcome>) - ensures os.Pass? ==> forall o | o in so :: o.Pass? - ensures os.Pass? <== forall o | o in so :: o.Pass? + ensures os.Pass? <==> forall o | o in so :: o.Pass? { var fails := Seq.Filter(so, (x: Outcome) => x.Fail?); if |fails| == 0 then From 928e9bed3f8e44a77bd522636ad884249cab69d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Fri, 19 Aug 2022 12:21:31 -0700 Subject: [PATCH 013/105] lib: Axiomatize a function version of Seq.Sort --- src/Utils/Lib.Sort.dfy | 23 ++++++++++++----------- src/Utils/Library.dfy | 4 ++-- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/Utils/Lib.Sort.dfy b/src/Utils/Lib.Sort.dfy index c5669675..dcefe9a4 100644 --- a/src/Utils/Lib.Sort.dfy +++ b/src/Utils/Lib.Sort.dfy @@ -404,21 +404,11 @@ module Utils.Lib.ArraySort { } } -module Utils.Lib.SetSort { +module Utils.Lib.SetSort { // TODO rename to Sort.Set when it becomes possible import opened C = Sort.Comparison import Array import ArraySort - // function Sort(st: set, cmp: Comparison): (sq: seq) - // requires cmp.Valid?(st) - // ensures st == set x <- sq - // ensures cmp.Sorted(sq) - // { - // [] - // } by method { - // sq := QuickSort(st, cmp); - // { - method QuickSort(st: set, cmp: Comparison) returns (sq: seq) requires cmp.Valid?(st) ensures st == set x <- sq @@ -441,4 +431,15 @@ module Utils.Lib.SetSort { } return arr[..]; } + + function {:opaque} Sort(st: set, cmp: Comparison): (sq: seq) + requires cmp.Valid?(st) + ensures st == set x <- sq + ensures cmp.Sorted(sq) + { + assume false; [] // TODO + } by method { + assume false; // TODO + sq := QuickSort(st, cmp); + } } diff --git a/src/Utils/Library.dfy b/src/Utils/Library.dfy index b6f4252b..19f1c65e 100644 --- a/src/Utils/Library.dfy +++ b/src/Utils/Library.dfy @@ -682,7 +682,7 @@ module Utils.Lib.Sort.Comparison { Complete?(ts) && /* Antisymmetric?(ts) && */ Transitive?(ts) } - predicate Total?(ts: set) { + predicate Total?(ts: set) { // TODO: Make this opaque? Automated proofs can get very slow and costly Complete?(ts) && Antisymmetric?(ts) && Transitive?(ts) } @@ -738,7 +738,7 @@ module Utils.Lib.Sort.DerivedComparison { lemma Total(ts: set) requires cmp.Total?(Set.Map(ts, fn)) - requires forall i, j :: fn(i) == fn(j) ==> i == j + requires forall i, j | fn(i) == fn(j) :: i == j ensures Comparison.Total?(ts) { Valid(ts); From d43f87b777fa13fafe41d0f188af412ec25ab5c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Fri, 19 Aug 2022 12:21:47 -0700 Subject: [PATCH 014/105] lib: Add Outcome.CombineSeq --- src/Utils/Library.dfy | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Utils/Library.dfy b/src/Utils/Library.dfy index 19f1c65e..1af21786 100644 --- a/src/Utils/Library.dfy +++ b/src/Utils/Library.dfy @@ -294,6 +294,17 @@ module Utils.Lib.Outcome.OfSeq { // FIXME rename to Seq assert fails[0] in so; Fail(Seq.Map((x: Outcome) requires x.Fail? => x.error, fails)) } + + function method CombineSeq(so: seq>>): (os: Outcome>) + ensures os.Pass? <==> forall o | o in so :: o.Pass? + { + var fails := Seq.Filter(so, (x: Outcome>) => x.Fail?); + if |fails| == 0 then + Pass + else + assert fails[0] in so; + Fail(Seq.Flatten(Seq.Map((x: Outcome>) requires x.Fail? => x.error, fails))) + } } module Utils.Lib.Str { From faab5c6b8246b20ae59f1641c6175053ecf9bdb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Fri, 19 Aug 2022 12:22:10 -0700 Subject: [PATCH 015/105] ast: Add an order on names --- src/AST/Names.dfy | 52 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/AST/Names.dfy b/src/AST/Names.dfy index ff6604ca..8be3bce1 100644 --- a/src/AST/Names.dfy +++ b/src/AST/Names.dfy @@ -1,4 +1,12 @@ +include "../Utils/Library.dfy" + module {:options "-functionSyntax:4"} Bootstrap.AST.Names { + import Utils.Lib.Set + import C = Utils.Lib.Sort.Comparison + import SeqCmp = Utils.Lib.Seq.Comparison + import StrCmp = Utils.Lib.Str.Comparison + import DerivedCmp = Utils.Lib.Sort.DerivedComparison + type Atom = // An individual component of a Dafny name. s: string | s != "" && '.' !in s witness "n" @@ -20,6 +28,36 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Names { case Name(parent, suffix) => parent.ToString() + "." + suffix } + function ToSeq(): seq { + match this + case Anonymous => [] + case Name(parent, suffix) => parent.ToSeq() + [suffix] + } + + static lemma {:induction false} ToSeqInj1(n0: Name, n1: Name) + requires n0.ToSeq() == n1.ToSeq() + decreases n0.Length() + ensures n0 == n1 + { + var s0, s1 := n0.ToSeq(), n1.ToSeq(); + if s0 != [] && s1 != [] { + assert s0 == s0[..|s0| - 1] + [s0[|s0| - 1]]; + assert s1 == s1[..|s1| - 1] + [s1[|s1| - 1]]; + assert n0.Name? && n1.Name?; + assert n1.suffix == s1[|s1| - 1] == s0[|s0| - 1] == n0.suffix; + assert n0.parent.ToSeq() == s0[..|s0| - 1] == s1[..|s1| - 1] == n1.parent.ToSeq(); + ToSeqInj1(n0.parent, n1.parent); + } + } + + static lemma ToSeqInj() + ensures forall n0: Name, n1: Name | n0.ToSeq() == n1.ToSeq() :: n0 == n1 + { + forall n0: Name, n1: Name | n0.ToSeq() == n1.ToSeq() { + ToSeqInj1(n0, n1); + } + } + predicate ChildOf(parent: Name) // Check whether `this` is a direct descendant of `parent`. ensures ChildOf(parent) ==> Length() == parent.Length() + 1 @@ -61,5 +99,19 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Names { { SuffixOf_Transitive(n0, n1, n2); } + + static const toSeq: Name -> seq := (name: Name) => name.ToSeq(); + static const AtomSeqComparison := SeqCmp.SeqComparison(StrCmp.Comparison); + static const NameComparison := DerivedCmp.DerivedComparison(AtomSeqComparison.Comparison, toSeq); + static const Comparison := NameComparison.Comparison; + + static lemma {:induction false} Total(names: set) + ensures Comparison.Total?(names) + { + ToSeqInj(); + StrCmp.Total(SeqCmp.Flatten(Set.Map(names, toSeq))); + AtomSeqComparison.Total(Set.Map(names, toSeq)); + NameComparison.Total(names); + } } } From 83684924f3ad8e2c64846589c71b2a25389523be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Fri, 19 Aug 2022 12:23:24 -0700 Subject: [PATCH 016/105] ast: Add a validation function on entities --- src/AST/Entities.dfy | 51 ++++++++++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/src/AST/Entities.dfy b/src/AST/Entities.dfy index 349f1378..99d91f6c 100644 --- a/src/AST/Entities.dfy +++ b/src/AST/Entities.dfy @@ -4,6 +4,7 @@ include "Names.dfy" include "Syntax.dfy" include "../Utils/Library.dfy" +include "../Utils/Lib.Sort.dfy" module {:options "-functionSyntax:4"} Bootstrap.AST.Entities // Hierarchies of Dafny entities. @@ -12,6 +13,8 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities import opened Names import opened Syntax.Exprs import opened Utils.Lib.Datatypes + import Utils.Lib.SetSort + import OS = Utils.Lib.Outcome.OfSeq datatype Module = Module(members: seq) @@ -123,6 +126,11 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities type EntityMap = f | EntityMap?(f) witness e => e + datatype ValidationError = + | NameMismatch(name: Name, key: Name) + | UnboundMember(name: Name, member: Name) + | UnboundParent(name: Name, parent: Name) + type Registry = r: Registry_ | r.Valid?() witness Registry_.EMPTY() datatype Registry_ = Registry(entities: map) @@ -140,38 +148,49 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities Registry(map[]) } - ghost predicate ValidName??(name: Name, entity: Entity) { + predicate ValidName??(name: Name, entity: Entity) { entity.ei.name == name } - ghost predicate ValidParent??(name: Name) { + predicate ValidParent??(name: Name) { name == Anonymous || name.parent in entities } - ghost predicate ValidMembers??(ei: EntityInfo) { + predicate ValidMembers??(ei: EntityInfo) { forall m <- ei.members :: m in entities } - ghost predicate ValidEntry??(name: Name, e: Entity) { - && ValidName??(name, e) + ghost predicate ValidEntry??(name: Name, entity: Entity) { + && ValidName??(name, entity) && ValidParent??(name) - && ValidMembers??(e.ei) - } - - ghost predicate ValidNames?() { - forall name <- entities :: ValidName??(name, entities[name]) + && ValidMembers??(entity.ei) } - ghost predicate ValidParents?() { - forall name <- entities :: ValidParent??(name) + ghost predicate Valid?() { + forall name <- entities :: ValidEntry??(name, entities[name]) } - ghost predicate ValidMembers?() { - forall name <- entities :: ValidMembers??(entities[name].ei) + function {:opaque} ValidateEntry(name: Name, entity: Entity): (o: Outcome>) + requires Lookup(name) == Some(entity) + ensures o.Pass? <==> ValidEntry??(name, entity) + { + assume false; // TODO + OS.Combine( + [if ValidName??(name, entity) then Pass else Fail(NameMismatch(name, entity.ei.name)), + if ValidParent??(name) then Pass else Fail(UnboundParent(name, name.parent))] + + Seq.Map(m => + if m in entities then Pass else Fail(UnboundMember(name, m)), + entity.ei.members) + ) } - ghost predicate Valid?() { - forall name <- entities :: ValidEntry??(name, entities[name]) + function {:opaque} Validate(): (o: Outcome>) + ensures o.Pass? <==> Valid?() + { + OS.CombineSeq( + Seq.Map(name requires name in entities => ValidateEntry(name, entities[name]), + SetSort.Sort(entities.Keys, Name.Comparison)) + ) } ghost function {:opaque} SuffixesOf(prefix: Name): set { From c0743a56e5a3ecc43f263a6fd078a477394589fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Fri, 19 Aug 2022 12:27:14 -0700 Subject: [PATCH 017/105] ast: Separate out known attributes in entities --- src/AST/Entities.dfy | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/AST/Entities.dfy b/src/AST/Entities.dfy index 99d91f6c..365fd9af 100644 --- a/src/AST/Entities.dfy +++ b/src/AST/Entities.dfy @@ -109,11 +109,24 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities case Definition(ei, d) => EDefinition } - datatype Attribute = // TODO: Move all exprs to top level? - Attribute(name: string, args: seq) + datatype AttributeName = + | Axiom + | Extern + | UserAttribute(name: string) { function ToString(): string { - "{:" + name + (if args != [] then " ..." else "") + "}" + match this + case Axiom => "axiom" + case Extern => "extern" + case UserAttribute(name) => name + } + } + + datatype Attribute = + Attribute(name: AttributeName, args: seq) + { + function ToString(): string { + "{:" + name.ToString() + (if args != [] then " ..." else "") + "}" } } From d168394f30dd82f560d909deeb23dbbb362e855f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Fri, 19 Aug 2022 12:28:04 -0700 Subject: [PATCH 018/105] ast: Make witness expressions optional --- src/AST/Entities.dfy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AST/Entities.dfy b/src/AST/Entities.dfy index 365fd9af..479bb63a 100644 --- a/src/AST/Entities.dfy +++ b/src/AST/Entities.dfy @@ -26,7 +26,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities Import(localName: Atom, target: Name) datatype SubsetType = - SubsetType(boundVar: string, pred: Expr, witnessExpr: Expr) + SubsetType(boundVar: string, pred: Expr, witnessExpr: Option) datatype TypeAlias = TypeAlias(base: Name) From 9e544b029473e6533f78b591079ee09e5bc07284 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Fri, 19 Aug 2022 12:28:40 -0700 Subject: [PATCH 019/105] ast: Add an API to retrieve the list of all names defined in the program --- src/AST/Entities.dfy | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/AST/Entities.dfy b/src/AST/Entities.dfy index 479bb63a..b8d780bb 100644 --- a/src/AST/Entities.dfy +++ b/src/AST/Entities.dfy @@ -272,6 +272,22 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities function Map(f: EntityMap): Registry requires Valid?() { Registry(map name | name in entities :: f(entities[name])) } + + // TODO Make a variant of SortedNames that traverses from the root and prove + // that it returns all entities. + + function {:opaque} SortedNames(): (all_names: seq) + ensures (set name <- all_names) == entities.Keys + { + SetSort.Sort(entities.Keys, Name.Comparison) + } + + // TODO + // function SortedEntities(): (all_entities: seq) + // ensures (set e <- all_entities :: e.ei.name) == entities.Keys + // { + // Seq.Map(name requires name in entities.Keys => entities[name], SortedNames()) + // } } type Program = p: Program_ | p.Valid?() witness Program.EMPTY() From 860dd0986c7e058cea765d982b2d0b9f95cde27d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Fri, 19 Aug 2022 12:29:02 -0700 Subject: [PATCH 020/105] ast: Add a field to the Module entity --- src/AST/Entities.dfy | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/AST/Entities.dfy b/src/AST/Entities.dfy index b8d780bb..0a010e2b 100644 --- a/src/AST/Entities.dfy +++ b/src/AST/Entities.dfy @@ -16,8 +16,10 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities import Utils.Lib.SetSort import OS = Utils.Lib.Outcome.OfSeq + // DISCUSS: Should this module be parameterized by `TExpr`? + datatype Module = - Module(members: seq) + Module() datatype ExportSet = ExportSet(provided: set, revealed: set) @@ -94,7 +96,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities } datatype Entity = - | Module(ei: EntityInfo) + | Module(ei: EntityInfo, m: Module) | ExportSet(ei: EntityInfo, e: ExportSet) | Import(ei: EntityInfo, i: Import) | Type(ei: EntityInfo, t: Type) @@ -102,7 +104,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities { const kind := match this - case Module(ei) => EModule + case Module(ei, m) => EModule case ExportSet(ei, e) => EExportSet case Import(ei, i) => EImport case Type(ei, t) => EType @@ -130,14 +132,14 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities } } - ghost predicate EntityMap?(f: Entity -> Entity) { + ghost predicate EntityTransformer?(f: Entity -> Entity) { forall e :: && f(e).kind == e.kind && f(e).ei.name == e.ei.name && f(e).ei.members == e.ei.members } - type EntityMap = f | EntityMap?(f) witness e => e + type EntityTransformer = f | EntityTransformer?(f) witness e => e datatype ValidationError = | NameMismatch(name: Name, key: Name) @@ -264,12 +266,12 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities requires Valid?() requires !Contains(name) requires entity.Module? - requires ValidEntry??(name, entity) + requires ValidEntry??(name, entity) // TODO: Only allows adding "Anonymous" { this.(entities := entities[name := entity]) } - function Map(f: EntityMap): Registry requires Valid?() { + function Map(f: EntityTransformer): Registry requires Valid?() { Registry(map name | name in entities :: f(entities[name])) } @@ -298,7 +300,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities { static function EMPTY(): (p: Program_) ensures p.Valid?() { Program( - Registry.EMPTY().Add(Anonymous, Entity.Module(EntityInfo.Mk(Anonymous))), + Registry.EMPTY().Add(Anonymous, Entity.Module(EntityInfo.Mk(Anonymous), Module.Module())), defaultModule := Anonymous, mainMethod := None ) @@ -309,7 +311,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities } predicate ValidMainMethod?() { - mainMethod.Some? ==> registry.HasKind(mainMethod.value, EModule) + mainMethod.Some? ==> registry.HasKind(mainMethod.value, EDefinition) // FIXME } predicate Valid?() { From 1cce2e7ae97386c41f35c4991d918aad86dbbde0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Wed, 24 Aug 2022 08:05:24 -0700 Subject: [PATCH 021/105] lib: Add `ensures` to `Seq.Flatten` --- src/Utils/Library.dfy | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Utils/Library.dfy b/src/Utils/Library.dfy index 1af21786..b591e365 100644 --- a/src/Utils/Library.dfy +++ b/src/Utils/Library.dfy @@ -94,6 +94,7 @@ module Utils.Lib.Datatypes { module Utils.Lib.Seq { // FIXME why not use a comprehension directly? + // FIXME move `f` function method {:opaque} Map(f: T ~> Q, ts: seq) : (qs: seq) reads f.reads requires forall t | t in ts :: f.requires(t) @@ -152,7 +153,10 @@ module Utils.Lib.Seq { if ts == [] then a0 else f(FoldL(f, a0, ts[1..]), ts[0]) } - function method Flatten(tss: seq>): seq { // TODO specify + function method Flatten(tss: seq>): (ts: seq) + ensures forall s <- ts :: exists ts <- tss :: s in ts + ensures forall ts0 <- tss, s <- ts0 :: s in ts + { if tss == [] then [] else tss[0] + Flatten(tss[1..]) } From 9fb131d41c3a963665555afa499249e432ee1404 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Wed, 24 Aug 2022 08:10:46 -0700 Subject: [PATCH 022/105] ast: Rename Name.SuffixOf to Name.ExtensionOf and add Name.PrefixOf --- src/AST/Entities.dfy | 4 +-- src/AST/Names.dfy | 78 +++++++++++++++++++++++++++++++++----------- 2 files changed, 61 insertions(+), 21 deletions(-) diff --git a/src/AST/Entities.dfy b/src/AST/Entities.dfy index 0a010e2b..2093947d 100644 --- a/src/AST/Entities.dfy +++ b/src/AST/Entities.dfy @@ -209,7 +209,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities } ghost function {:opaque} SuffixesOf(prefix: Name): set { - set name <- entities | name.SuffixOf(prefix) + set name <- entities | name.ExtensionOf(prefix) } ghost function {:opaque} SuffixesOfMany(prefixes: seq): set { @@ -228,7 +228,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities ensures name in SuffixesOf(ei.name) { var prefix: Name :| prefix in ei.members && name in SuffixesOf(prefix); - Name.SuffixOf_Transitive(ei.name, prefix, name); + Name.ExtensionOf_Transitive(ei.name, prefix, name); } } diff --git a/src/AST/Names.dfy b/src/AST/Names.dfy index 8be3bce1..b8a73dfb 100644 --- a/src/AST/Names.dfy +++ b/src/AST/Names.dfy @@ -65,39 +65,79 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Names { this.Name? && parent == this.parent } - predicate StrictSuffixOf(parent: Name) + predicate ParentOf(child: Name) + // Check whether `this` is a direct parent of `child`. + ensures ParentOf(child) ==> Length() + 1 == child.Length() + { + child.ChildOf(this) + } + + predicate StrictPrefixOf(child: Name) + // Check whether `this` is an indirect parent of `child`. + ensures StrictPrefixOf(child) ==> Length() < child.Length() + { + this != child && PrefixOf(child) + } + + predicate PrefixOf(child: Name) + // Check whether `this` is a direct or indirect parent of `child`. + ensures PrefixOf(child) ==> Length() <= child.Length() + { + || child == this + || (child.Name? && PrefixOf(child.parent)) + } + + static lemma {:induction n2} PrefixOf_Transitive(n0: Name, n1: Name, n2: Name) + requires n0.PrefixOf(n1) && n1.PrefixOf(n2) + ensures n0.PrefixOf(n2) + {} + + static lemma {:induction false} StrictPrefixOf_Left_Transitive(n0: Name, n1: Name, n2: Name) + requires n0.StrictPrefixOf(n1) && n1.PrefixOf(n2) + ensures n0.StrictPrefixOf(n2) + { + PrefixOf_Transitive(n0, n1, n2); + } + + static lemma {:induction false} StrictPrefixOf_Right_Transitive(n0: Name, n1: Name, n2: Name) + requires n0.PrefixOf(n1) && n1.StrictPrefixOf(n2) + ensures n0.StrictPrefixOf(n2) + { + PrefixOf_Transitive(n0, n1, n2); + } + + predicate StrictExtensionOf(parent: Name) // Check whether one of the parents of `this` is `parent`. - ensures StrictSuffixOf(parent) ==> Length() > parent.Length() + ensures StrictExtensionOf(parent) ==> Length() > parent.Length() { - this != parent && SuffixOf(parent) + parent.StrictPrefixOf(this) } - predicate SuffixOf(parent: Name) - // Check whether `this` descended from `parent`. + predicate ExtensionOf(parent: Name) + // Check whether `this` is descended from `parent`. decreases this - ensures SuffixOf(parent) ==> Length() >= parent.Length() + ensures ExtensionOf(parent) ==> Length() >= parent.Length() { - || parent == this - || (this.Name? && this.parent.SuffixOf(parent)) + parent.PrefixOf(this) } - static lemma {:induction n2} SuffixOf_Transitive(n0: Name, n1: Name, n2: Name) - requires n2.SuffixOf(n1) && n1.SuffixOf(n0) - ensures n2.SuffixOf(n0) + static lemma {:induction n2} ExtensionOf_Transitive(n0: Name, n1: Name, n2: Name) + requires n2.ExtensionOf(n1) && n1.ExtensionOf(n0) + ensures n2.ExtensionOf(n0) {} - static lemma {:induction false} StrictSuffixOf_Left_Transitive(n0: Name, n1: Name, n2: Name) - requires n2.StrictSuffixOf(n1) && n1.SuffixOf(n0) - ensures n2.StrictSuffixOf(n0) + static lemma {:induction false} StrictExtensionOf_Left_Transitive(n0: Name, n1: Name, n2: Name) + requires n2.StrictExtensionOf(n1) && n1.ExtensionOf(n0) + ensures n2.StrictExtensionOf(n0) { - SuffixOf_Transitive(n0, n1, n2); + ExtensionOf_Transitive(n0, n1, n2); } - static lemma {:induction false} StrictSuffixOf_Right_Transitive(n0: Name, n1: Name, n2: Name) - requires n2.SuffixOf(n1) && n1.StrictSuffixOf(n0) - ensures n2.StrictSuffixOf(n0) + static lemma {:induction false} StrictExtensionOf_Right_Transitive(n0: Name, n1: Name, n2: Name) + requires n2.ExtensionOf(n1) && n1.StrictExtensionOf(n0) + ensures n2.StrictExtensionOf(n0) { - SuffixOf_Transitive(n0, n1, n2); + ExtensionOf_Transitive(n0, n1, n2); } static const toSeq: Name -> seq := (name: Name) => name.ToSeq(); From dc04167e4db8324264bb47dc4c75dfb9dbbd4e80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Wed, 24 Aug 2022 08:12:45 -0700 Subject: [PATCH 023/105] ast: Use `Registry.Contains(name)` instead of `name in entities` in specs --- src/AST/Entities.dfy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AST/Entities.dfy b/src/AST/Entities.dfy index 2093947d..db1ac0d7 100644 --- a/src/AST/Entities.dfy +++ b/src/AST/Entities.dfy @@ -217,7 +217,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities } lemma Decreases_SuffixesOfMany(ei: EntityInfo) - requires ei.name in entities + requires Contains(ei.name) ensures SuffixesOfMany(ei.members) < SuffixesOf(ei.name); { reveal SuffixesOf(); From 60429bb236d1603ffb1e3474bf2e83f92a67efd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Wed, 24 Aug 2022 14:47:56 -0700 Subject: [PATCH 024/105] ast: Define `Name.NthParent` and `Name.TruncateTo` --- src/AST/Names.dfy | 53 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/src/AST/Names.dfy b/src/AST/Names.dfy index b8a73dfb..ea845980 100644 --- a/src/AST/Names.dfy +++ b/src/AST/Names.dfy @@ -76,7 +76,8 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Names { // Check whether `this` is an indirect parent of `child`. ensures StrictPrefixOf(child) ==> Length() < child.Length() { - this != child && PrefixOf(child) + || ParentOf(child) + || (child.Name? && StrictPrefixOf(child.parent)) } predicate PrefixOf(child: Name) @@ -84,7 +85,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Names { ensures PrefixOf(child) ==> Length() <= child.Length() { || child == this - || (child.Name? && PrefixOf(child.parent)) + || StrictPrefixOf(child) } static lemma {:induction n2} PrefixOf_Transitive(n0: Name, n1: Name, n2: Name) @@ -140,6 +141,54 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Names { ExtensionOf_Transitive(n0, n1, n2); } + function NthParent(n: nat): (nth_parent: Name) + requires n <= Length() + ensures ExtensionOf(nth_parent) + ensures nth_parent.Length() == Length() - n + { + if n == 0 then this + else this.parent.NthParent(n - 1) + } + + lemma NthParent_Extension(ancestor: Name, n: nat) + requires ExtensionOf(ancestor) + requires n <= Length() - ancestor.Length() + ensures NthParent(n).ExtensionOf(ancestor) + {} + + function TruncateTo(length: nat): (truncated: Name) + requires length <= Length() + ensures ExtensionOf(truncated) + ensures truncated.Length() == length + { + NthParent(Length() - length) + } + + lemma TruncateTo_Extension(ancestor: Name, length: nat) + requires ExtensionOf(ancestor) + requires ancestor.Length() <= length <= Length() + ensures TruncateTo(length).ExtensionOf(ancestor) + { + NthParent_Extension(ancestor, Length() - length); + } + + lemma Length_ChildOf(parent: Name) + requires Length() == parent.Length() + 1 + requires ExtensionOf(parent) + ensures ChildOf(parent) + {} + + function ChildOfAncestor(ancestor: Name): (child: Name) + requires StrictExtensionOf(ancestor) + ensures ExtensionOf(child) + ensures child.ChildOf(ancestor) + { + var child := TruncateTo(ancestor.Length() + 1); + TruncateTo_Extension(ancestor, ancestor.Length() + 1); + child.Length_ChildOf(ancestor); + child + } + static const toSeq: Name -> seq := (name: Name) => name.ToSeq(); static const AtomSeqComparison := SeqCmp.SeqComparison(StrCmp.Comparison); static const NameComparison := DerivedCmp.DerivedComparison(AtomSeqComparison.Comparison, toSeq); From 75e76aca1866a279e6d5187e98c0c487453eb237 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Wed, 24 Aug 2022 14:50:28 -0700 Subject: [PATCH 025/105] ast: Rename `Registry.SuffixesOf` to `Registry.TransitiveMembers` --- src/AST/Entities.dfy | 40 ++++++++++++++++++++-------------------- src/Debug/Entities.dfy | 8 ++++---- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/AST/Entities.dfy b/src/AST/Entities.dfy index db1ac0d7..41a20882 100644 --- a/src/AST/Entities.dfy +++ b/src/AST/Entities.dfy @@ -153,9 +153,9 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities // // The entity graph of a Dafny program is a tree: members of a module or // class have names that extend that of the parent module. This fact allows - // easy recursion, using the functions `SuffixesOf` and `SuffixesOfMany` - // below, along with the two recursion lemmas `Decreases_SuffixesOf` and - // `Decreases_SuffixesOfMany`. + // easy recursion, using the functions `TransitiveMembers` and `TransitiveMembersOfMany` + // below, along with the two recursion lemmas `Decreases_TransitiveMembers` and + // `Decreases_TransitiveMembersOfMany`. { static function EMPTY(): (r: Registry_) ensures r.Valid?() @@ -208,40 +208,40 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities ) } - ghost function {:opaque} SuffixesOf(prefix: Name): set { + ghost function {:opaque} TransitiveMembers(prefix: Name): set { set name <- entities | name.ExtensionOf(prefix) } - ghost function {:opaque} SuffixesOfMany(prefixes: seq): set { - set prefix <- prefixes, name <- SuffixesOf(prefix) :: name + ghost function {:opaque} TransitiveMembersOfMany(prefixes: seq): set { + set prefix <- prefixes, name <- TransitiveMembers(prefix) :: name } - lemma Decreases_SuffixesOfMany(ei: EntityInfo) + lemma Decreases_TransitiveMembersOfMany(ei: EntityInfo) requires Contains(ei.name) - ensures SuffixesOfMany(ei.members) < SuffixesOf(ei.name); + ensures TransitiveMembersOfMany(ei.members) < TransitiveMembers(ei.name); { - reveal SuffixesOf(); - reveal SuffixesOfMany(); + reveal TransitiveMembers(); + reveal TransitiveMembersOfMany(); - assert SuffixesOfMany(ei.members) <= SuffixesOf(ei.name) by { - forall name <- SuffixesOfMany(ei.members) - ensures name in SuffixesOf(ei.name) + assert TransitiveMembersOfMany(ei.members) <= TransitiveMembers(ei.name) by { + forall name <- TransitiveMembersOfMany(ei.members) + ensures name in TransitiveMembers(ei.name) { - var prefix: Name :| prefix in ei.members && name in SuffixesOf(prefix); + var prefix: Name :| prefix in ei.members && name in TransitiveMembers(prefix); Name.ExtensionOf_Transitive(ei.name, prefix, name); } } - assert ei.name in SuffixesOf(ei.name); - assert ei.name !in SuffixesOfMany(ei.members); + assert ei.name in TransitiveMembers(ei.name); + assert ei.name !in TransitiveMembersOfMany(ei.members); } - lemma {:induction false} Decreases_SuffixesOf(names: seq, name: Name) + lemma {:induction false} Decreases_TransitiveMembers(names: seq, name: Name) requires name in names - ensures SuffixesOf(name) <= SuffixesOfMany(names); + ensures TransitiveMembers(name) <= TransitiveMembersOfMany(names); { - reveal SuffixesOf(); - reveal SuffixesOfMany(); + reveal TransitiveMembers(); + reveal TransitiveMembersOfMany(); } predicate Contains(name: Name) { diff --git a/src/Debug/Entities.dfy b/src/Debug/Entities.dfy index 8571b4fc..2008a378 100644 --- a/src/Debug/Entities.dfy +++ b/src/Debug/Entities.dfy @@ -37,22 +37,22 @@ module {:options "-functionSyntax:4"} Bootstrap.Debug.Entities { method DumpEntity(name: Name, indent: string) requires registry.Contains(name) - decreases registry.SuffixesOf(name), 0 + decreases registry.TransitiveMembers(name), 0 { var entity := registry.Get(name); DumpEntityHeader(entity.ei, indent); print Subtitle("Members", indent); - registry.Decreases_SuffixesOfMany(entity.ei); + registry.Decreases_TransitiveMembersOfMany(entity.ei); DumpEntities(entity.ei.members, indent + INDENT); } method DumpEntities(names: seq, indent: string) requires forall name <- names :: registry.Contains(name) - decreases registry.SuffixesOfMany(names), 1 + decreases registry.TransitiveMembersOfMany(names), 1 { for i := 0 to |names| { var name := names[i]; - registry.Decreases_SuffixesOf(names, name); + registry.Decreases_TransitiveMembers(names, name); DumpEntity(name, indent); print "\n"; } From 30f28befa7abe42df0930b2982683f39bef038a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Wed, 24 Aug 2022 14:53:42 -0700 Subject: [PATCH 026/105] ast: Strengthen registry's validity criterion * `src/AST/Entities.dfy` (`ValidParent??`): Force entities to mention all of their members. --- src/AST/Entities.dfy | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/AST/Entities.dfy b/src/AST/Entities.dfy index 41a20882..6e609e9e 100644 --- a/src/AST/Entities.dfy +++ b/src/AST/Entities.dfy @@ -168,7 +168,15 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities } predicate ValidParent??(name: Name) { - name == Anonymous || name.parent in entities + || name == Anonymous + || (name.parent in entities && MemberOf(name, name.parent)) + } + + predicate MemberOf(name: Name, parent: Name) + requires name.Name? + requires Contains(name.parent) + { + name in entities[name.parent].ei.members } predicate ValidMembers??(ei: EntityInfo) { From 76ecbd8ec868ea1b9a9a05329b003fef84aff0bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Wed, 24 Aug 2022 14:56:05 -0700 Subject: [PATCH 027/105] ast: Prove more general lemmas about recursive traversals --- src/AST/Entities.dfy | 214 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 176 insertions(+), 38 deletions(-) diff --git a/src/AST/Entities.dfy b/src/AST/Entities.dfy index 6e609e9e..141c901b 100644 --- a/src/AST/Entities.dfy +++ b/src/AST/Entities.dfy @@ -154,8 +154,8 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities // The entity graph of a Dafny program is a tree: members of a module or // class have names that extend that of the parent module. This fact allows // easy recursion, using the functions `TransitiveMembers` and `TransitiveMembersOfMany` - // below, along with the two recursion lemmas `Decreases_TransitiveMembers` and - // `Decreases_TransitiveMembersOfMany`. + // below, along with the recursion lemmas `Decreases_TransitiveMembers_Single`, + // `Decreases_TransitiveMembers_Many`, and `Decreases_TransitiveMembersOfMany`. { static function EMPTY(): (r: Registry_) ensures r.Valid?() @@ -163,6 +163,9 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities Registry(map[]) } +/// Well-formedness +/// ~~~~~~~~~~~~~~~ + predicate ValidName??(name: Name, entity: Entity) { entity.ei.name == name } @@ -193,6 +196,9 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities forall name <- entities :: ValidEntry??(name, entities[name]) } +/// Post-construction validation +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + function {:opaque} ValidateEntry(name: Name, entity: Entity): (o: Outcome>) requires Lookup(name) == Some(entity) ensures o.Pass? <==> ValidEntry??(name, entity) @@ -216,44 +222,31 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities ) } - ghost function {:opaque} TransitiveMembers(prefix: Name): set { - set name <- entities | name.ExtensionOf(prefix) - } +/// Core API +/// ~~~~~~~~ - ghost function {:opaque} TransitiveMembersOfMany(prefixes: seq): set { - set prefix <- prefixes, name <- TransitiveMembers(prefix) :: name + predicate Contains(name: Name) { + name in entities } - lemma Decreases_TransitiveMembersOfMany(ei: EntityInfo) - requires Contains(ei.name) - ensures TransitiveMembersOfMany(ei.members) < TransitiveMembers(ei.name); + lemma {:induction false} NthParent_Contains(name: Name, n: nat) + requires Valid?() + requires Contains(name) + requires n <= name.Length() + ensures Contains(name.NthParent(n)) { - reveal TransitiveMembers(); - reveal TransitiveMembersOfMany(); - - assert TransitiveMembersOfMany(ei.members) <= TransitiveMembers(ei.name) by { - forall name <- TransitiveMembersOfMany(ei.members) - ensures name in TransitiveMembers(ei.name) - { - var prefix: Name :| prefix in ei.members && name in TransitiveMembers(prefix); - Name.ExtensionOf_Transitive(ei.name, prefix, name); - } + if n > 0 { + NthParent_Contains(name.parent, n - 1); } - - assert ei.name in TransitiveMembers(ei.name); - assert ei.name !in TransitiveMembersOfMany(ei.members); } - lemma {:induction false} Decreases_TransitiveMembers(names: seq, name: Name) - requires name in names - ensures TransitiveMembers(name) <= TransitiveMembersOfMany(names); + lemma {:induction false} TruncateTo_Contains(name: Name, length: nat) + requires Valid?() + requires Contains(name) + requires length <= name.Length() + ensures Contains(name.TruncateTo(length)) { - reveal TransitiveMembers(); - reveal TransitiveMembersOfMany(); - } - - predicate Contains(name: Name) { - name in entities + NthParent_Contains(name, name.Length() - length); } function Get(name: Name): Entity @@ -270,6 +263,12 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities Contains(name) && Get(name).kind == kind } + function Members(name: Name): seq + requires Contains(name) + { + Get(name).ei.members + } + function Add(name: Name, entity: Entity): Registry requires Valid?() requires !Contains(name) @@ -292,12 +291,151 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities SetSort.Sort(entities.Keys, Name.Comparison) } - // TODO - // function SortedEntities(): (all_entities: seq) - // ensures (set e <- all_entities :: e.ei.name) == entities.Keys - // { - // Seq.Map(name requires name in entities.Keys => entities[name], SortedNames()) - // } +/// Unordered traversals +/// ~~~~~~~~~~~~~~~~~~~~ + + function {:opaque} TransitiveMembers(prefix: Name): set { + set name <- entities | name.ExtensionOf(prefix) + } + + function {:opaque} TransitiveMembersOfMany(prefixes: seq): set { + set prefix <- prefixes, name <- TransitiveMembers(prefix) :: name + } + + lemma TransitiveMembersOfMany_Le(root: Name, members: seq) + requires forall member <- members :: member.ChildOf(root) + ensures TransitiveMembersOfMany(members) <= TransitiveMembers(root); + { + reveal TransitiveMembers(); + reveal TransitiveMembersOfMany(); + + forall name <- TransitiveMembersOfMany(members) + ensures name in TransitiveMembers(root) + { + var prefix: Name :| prefix in members && name in TransitiveMembers(prefix); + Name.ExtensionOf_Transitive(root, prefix, name); + } + } + + lemma TransitiveMembersOfMany_Lt(root: Name, members: seq) + requires Contains(root) + requires forall member <- members :: member.ChildOf(root) + ensures TransitiveMembersOfMany(members) < TransitiveMembers(root); + { + reveal TransitiveMembers(); + reveal TransitiveMembersOfMany(); + + TransitiveMembersOfMany_Le(root, members); + + assert root in TransitiveMembers(root); + assert root !in TransitiveMembersOfMany(members); + } + + lemma {:induction false} TransitiveMembers_Le_Many(names: seq, name: Name) + requires name in names + ensures TransitiveMembers(name) <= TransitiveMembersOfMany(names); + { + reveal TransitiveMembers(); + reveal TransitiveMembersOfMany(); + } + + lemma {:induction false} TransitiveMembers_Le(root: Name, member: Name) + requires member.ChildOf(root) + ensures TransitiveMembers(member) <= TransitiveMembers(root); + { + TransitiveMembersOfMany_Le(root, [member]); + TransitiveMembers_Le_Many([member], member); + } + + lemma {:induction false} TransitiveMembers_Lt(root: Name, member: Name) + requires Contains(root) + requires member.ChildOf(root) + ensures TransitiveMembers(member) < TransitiveMembers(root); + { + TransitiveMembersOfMany_Lt(root, [member]); + TransitiveMembers_Le_Many([member], member); + } + + lemma {:induction false} TransitiveMembers_Extension_Le(root: Name, name: Name) + requires name.ExtensionOf(root) + ensures TransitiveMembers(name) <= TransitiveMembers(root) + { + if name != root { + TransitiveMembers_Le(name.parent, name); + TransitiveMembers_Extension_Le(root, name.parent); + } + } + + lemma {:induction false} Decreases_TransitiveMembers_Many(names: seq, name: Name) + requires name in names + ensures TransitiveMembers(name) <= TransitiveMembersOfMany(names); + { + TransitiveMembers_Le_Many(names, name); + } + + lemma {:induction false} Decreases_TransitiveMembers_Single(ei: EntityInfo, member: Name) + requires Contains(ei.name) + requires member in ei.members + ensures TransitiveMembers(member) < TransitiveMembers(ei.name); + { + TransitiveMembers_Lt(ei.name, member); + } + + lemma {:induction false} Decreases_TransitiveMembersOfMany(ei: EntityInfo) + requires Contains(ei.name) + ensures TransitiveMembersOfMany(ei.members) < TransitiveMembers(ei.name); + { + TransitiveMembersOfMany_Lt(ei.name, ei.members); + } + + + function {:opaque} StrictTransitiveMembers(root: Name): (descendants: set) { + set name <- entities | name.StrictExtensionOf(root) + } + + lemma TransitiveMembers_StrictTransitiveMembers(root: Name) + requires Contains(root) + ensures TransitiveMembers(root) == {root} + StrictTransitiveMembers(root) + { + reveal TransitiveMembers(); + reveal StrictTransitiveMembers(); + } + + ghost function StrictTransitiveMembers_Partitioned(root: Name): set + requires Contains(root) + { + set member <- Members(root), name <- TransitiveMembers(member) :: name + } + + lemma StrictTransitiveMembers_Partition(root: Name) + requires Valid?() + requires Contains(root) + ensures StrictTransitiveMembers(root) == StrictTransitiveMembers_Partitioned(root) + { + reveal TransitiveMembers(); + reveal StrictTransitiveMembers(); + + var members := Members(root); + + forall name <- StrictTransitiveMembers(root) + ensures name in StrictTransitiveMembers_Partitioned(root) + { + var member := name.ChildOfAncestor(root); + assert Contains(member) by { TruncateTo_Contains(name, root.Length() + 1); } + assert member in members; + assert name in TransitiveMembers(member); + assert name in StrictTransitiveMembers_Partitioned(root); + } + + forall name <- StrictTransitiveMembers_Partitioned(root) + ensures name in StrictTransitiveMembers(root) + { + var member :| member in members && name in TransitiveMembers(member); + Name.StrictExtensionOf_Right_Transitive(root, member, name); + assert name in StrictTransitiveMembers(root); + } + } + } type Program = p: Program_ | p.Valid?() witness Program.EMPTY() From 7be7075f759a6fe284c0f23dd5e64f1f4e4dda33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Wed, 24 Aug 2022 14:56:25 -0700 Subject: [PATCH 028/105] ast: Add and prove a depth-first traversal of the entity graph --- src/AST/Entities.dfy | 90 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/src/AST/Entities.dfy b/src/AST/Entities.dfy index 141c901b..c5670a26 100644 --- a/src/AST/Entities.dfy +++ b/src/AST/Entities.dfy @@ -436,6 +436,96 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities } } +/// Depth-first traversal +/// ~~~~~~~~~~~~~~~~~~~~~ + + function RecursiveTransitiveMembers(root: Name): (members: seq) + requires Valid?() + requires Contains(root) + decreases TransitiveMembers(root), 1 + { + // DISCUSS: We need to name the lambda to prove anything about it below + [root] + Seq.Flatten(Seq.Map(RecursiveTransitiveMembers'(root), Members(root))) + } + + function RecursiveTransitiveMembers'(ghost root: Name) + : (Name --> seq) + requires Valid?() + requires Contains(root) + decreases TransitiveMembers(root), 0 + { + member requires member in Members(root) => + TransitiveMembers_Lt(root, member); + RecursiveTransitiveMembers(member) + } + + lemma RecursiveTransitiveMembers_Extension(root: Name) + requires Valid?() + requires Contains(root) + ensures forall d <- RecursiveTransitiveMembers(root) :: d.ExtensionOf(root) + { + forall d <- RecursiveTransitiveMembers(root) + ensures d.ExtensionOf(root) + { + RecursiveTransitiveMembers_Eq(root); + assert d in TransitiveMembers(root); + reveal TransitiveMembers(); + } + } + + lemma {:induction false} RecursiveTransitiveMembers_Le(root: Name, name: Name) + requires Valid?() + requires Contains(root) + decreases TransitiveMembers(root) + requires name in RecursiveTransitiveMembers(root) + ensures name in TransitiveMembers(root) + { + reveal TransitiveMembers(); + if name != root { + assert name in Seq.Flatten(Seq.Map(RecursiveTransitiveMembers'(root), Members(root))); + var member :| member in Members(root) && name in RecursiveTransitiveMembers(member); + TransitiveMembers_Lt(root, member); + RecursiveTransitiveMembers_Le(member, name); + TransitiveMembers_Extension_Le(root, member); + } + } + + lemma {:induction false} RecursiveTransitiveMembers_Ge(root: Name, name: Name) + requires Valid?() + requires Contains(root) + decreases TransitiveMembers(root) + requires name in TransitiveMembers(root) + ensures name in RecursiveTransitiveMembers(root) + { + reveal TransitiveMembers(); + if name != root { + assert name in StrictTransitiveMembers(root) by { + TransitiveMembers_StrictTransitiveMembers(root); + } + StrictTransitiveMembers_Partition(root); + assert name in StrictTransitiveMembers_Partitioned(root); + var member :| member in Members(root) && name in TransitiveMembers(member); + TransitiveMembers_Lt(root, member); + RecursiveTransitiveMembers_Ge(member, name); + } + } + + lemma {:induction false} RecursiveTransitiveMembers_Eq(root: Name) + requires Valid?() + requires Contains(root) + ensures Set.OfSeq(RecursiveTransitiveMembers(root)) == TransitiveMembers(root) + { + forall name ensures name in Set.OfSeq(RecursiveTransitiveMembers(root)) + <==> name in TransitiveMembers(root) + { + if name in Set.OfSeq(RecursiveTransitiveMembers(root)) { + RecursiveTransitiveMembers_Le(root, name); + } + if name in TransitiveMembers(root) { + RecursiveTransitiveMembers_Ge(root, name); + } + } + } } type Program = p: Program_ | p.Valid?() witness Program.EMPTY() From f025b4586304bd5bc4e658c144c0d25d393d031a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Wed, 24 Aug 2022 14:57:01 -0700 Subject: [PATCH 029/105] ast: Add a registry API to retrieve all names --- src/AST/Entities.dfy | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/AST/Entities.dfy b/src/AST/Entities.dfy index c5670a26..0c01a6e6 100644 --- a/src/AST/Entities.dfy +++ b/src/AST/Entities.dfy @@ -285,6 +285,12 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities // TODO Make a variant of SortedNames that traverses from the root and prove // that it returns all entities. + function {:opaque} AllNames(): set + ensures forall name :: name in AllNames() <==> Contains(name) + { + entities.Keys + } + function {:opaque} SortedNames(): (all_names: seq) ensures (set name <- all_names) == entities.Keys { From ebb4cd90d6dc01d59e6d93793dfa806b79fc2a26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Wed, 24 Aug 2022 16:32:25 -0700 Subject: [PATCH 030/105] transforms: Fix typo introduced in 6945382 --- src/Transforms/Generic.dfy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Transforms/Generic.dfy b/src/Transforms/Generic.dfy index a9ea714f..137c6f85 100644 --- a/src/Transforms/Generic.dfy +++ b/src/Transforms/Generic.dfy @@ -24,7 +24,7 @@ module Bootstrap.Transforms.Generic { } predicate Valid?() { - forall a | f.requires(a) :: HasValidPost() + HasValidPost() } } From f18151043acf05d488ffcf460820339c790501ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Wed, 24 Aug 2022 16:32:53 -0700 Subject: [PATCH 031/105] lib: Make `Comparison.Valid?` and `Comparison.Total?` opaque --- src/Utils/Lib.Sort.dfy | 13 +++++++++---- src/Utils/Library.dfy | 29 ++++++++++++++++++++++++----- 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/src/Utils/Lib.Sort.dfy b/src/Utils/Lib.Sort.dfy index dcefe9a4..550e924d 100644 --- a/src/Utils/Lib.Sort.dfy +++ b/src/Utils/Lib.Sort.dfy @@ -86,6 +86,7 @@ module Utils.Lib.ArraySort { requires 0 <= lo <= hi <= arr.Length reads arr { + reveal C.Valid?(); // Leaks through C.Valid?(Set.OfSlice(arr, lo, hi)) } @@ -188,10 +189,11 @@ module Utils.Lib.ArraySort { lemma StripedNonEmpty(sq: seq, pivot: T, lo: int, left: int, mid: int, right: int, hi: int) requires 0 <= lo <= left <= mid <= right <= hi <= |sq| requires C.Striped(sq, pivot, lo, left, mid, right, hi) - requires C.Valid?(set x <- sq[lo..hi]) + requires C.Valid?(Set.OfSeq(sq[lo..hi])) requires pivot in sq[lo..hi] ensures left < right { + reveal C.Valid?(); var idx :| lo <= idx < hi && sq[idx] == pivot; C.AlwaysReflexive(pivot); reveal C.Striped(); @@ -406,12 +408,14 @@ module Utils.Lib.ArraySort { module Utils.Lib.SetSort { // TODO rename to Sort.Set when it becomes possible import opened C = Sort.Comparison + import Set import Array import ArraySort method QuickSort(st: set, cmp: Comparison) returns (sq: seq) requires cmp.Valid?(st) - ensures st == set x <- sq + ensures multiset(st) == multiset(sq) + ensures st == Set.OfSeq(sq) ensures cmp.Sorted(sq) { var arr := Array.OfSet(st); @@ -422,7 +426,7 @@ module Utils.Lib.SetSort { // TODO rename to Sort.Set when it becomes possible assert arr[0..arr.Length] == arr[..]; assert old@unsorted(arr[0..arr.Length]) == old@unsorted(arr[..]); calc { - set x <- arr[..]; + Set.OfSeq(arr[..]); set x <- multiset(arr[..]); set x <- old@unsorted(multiset(arr[..])); set x <- multiset(st); @@ -434,7 +438,8 @@ module Utils.Lib.SetSort { // TODO rename to Sort.Set when it becomes possible function {:opaque} Sort(st: set, cmp: Comparison): (sq: seq) requires cmp.Valid?(st) - ensures st == set x <- sq + ensures multiset(st) == multiset(sq) + ensures st == Set.OfSeq(sq) ensures cmp.Sorted(sq) { assume false; [] // TODO diff --git a/src/Utils/Library.dfy b/src/Utils/Library.dfy index b591e365..0e2224dd 100644 --- a/src/Utils/Library.dfy +++ b/src/Utils/Library.dfy @@ -105,6 +105,11 @@ module Utils.Lib.Seq { if ts == [] then [] else [f(ts[0])] + Map(f, ts[1..]) } + lemma Map_in(f: T ~> Q, ts: seq) + requires forall t | t in ts :: f.requires(t) + ensures forall q :: q in Map(f, ts) <==> exists t | t in ts :: q == f(t) + {} + function method FoldL(f: (TAcc, T) ~> TAcc, a0: TAcc, ts: seq) : TAcc reads f.reads requires forall a, t | t in ts :: f.requires(a, t) @@ -473,7 +478,9 @@ module Utils.Lib.Int.Comparison { lemma {:induction ints} Total(ints: set) ensures Comparison.Total?(ints) - {} + { + reveal Comparison.Total?(); + } } module Utils.Lib.Char.Comparison { @@ -489,7 +496,9 @@ module Utils.Lib.Char.Comparison { lemma {:induction chars} Total(chars: set) ensures Comparison.Total?(chars) - {} + { + reveal Comparison.Total?(); + } } module Utils.Lib.Seq.Comparison { @@ -517,6 +526,7 @@ module Utils.Lib.Seq.Comparison { requires cmp.Total?(Set.OfSeq(s0) + Set.OfSeq(s1)) ensures Comparison.Complete??(s0, s1) { + reveal cmp.Total?(); if s0 != [] && s1 != [] { assert s0[0] in s0 && s1[0] in s1; assert s0[0] in Set.OfSeq(s0) && s1[0] in Set.OfSeq(s1); @@ -528,7 +538,7 @@ module Utils.Lib.Seq.Comparison { requires cmp.Total?(Set.OfSeq(s0) + Set.OfSeq(s1)) ensures Comparison.Antisymmetric??(s0, s1) { - // forall s0, s1 | s0 in tss && s1 in tss ensures Comparison.Complete??(s0, s1) { + reveal cmp.Total?(); if s0 != [] && s1 != [] { assert s0[0] in s0 && s1[0] in s1; assert s0[0] in Set.OfSeq(s0) && s1[0] in Set.OfSeq(s1); @@ -541,6 +551,7 @@ module Utils.Lib.Seq.Comparison { requires cmp.Total?(Set.OfSeq(s0) + Set.OfSeq(s1) + Set.OfSeq(s2)) ensures Comparison.Transitive??(s0, s1, s2) { + reveal cmp.Total?(); if s0 != [] && s1 != [] && s1 != [] && Comparison.Compare(s0, s1).Le? && Comparison.Compare(s1, s2).Le? { assert s0[0] in s0 && s1[0] in s1 && s2[0] in s2; assert s0[0] in Set.OfSeq(s0) && s1[0] in Set.OfSeq(s1) && s2[0] in Set.OfSeq(s2); @@ -557,6 +568,8 @@ module Utils.Lib.Seq.Comparison { requires cmp.Total?(Flatten(tss)) ensures Comparison.Total?(tss) { + reveal cmp.Total?(); + reveal Comparison.Total?(); forall s0, s1 | s0 in tss && s1 in tss ensures Comparison.Complete??(s0, s1) { Complete1(s0, s1); } @@ -693,11 +706,11 @@ module Utils.Lib.Sort.Comparison { forall t0, t1, t2 | t0 in ts && t1 in ts && t2 in ts :: Transitive??(t0, t1, t2) } - predicate Valid?(ts: set) { + predicate {:opaque} Valid?(ts: set) { Complete?(ts) && /* Antisymmetric?(ts) && */ Transitive?(ts) } - predicate Total?(ts: set) { // TODO: Make this opaque? Automated proofs can get very slow and costly + predicate {:opaque} Total?(ts: set) { Complete?(ts) && Antisymmetric?(ts) && Transitive?(ts) } @@ -743,6 +756,8 @@ module Utils.Lib.Sort.DerivedComparison { requires cmp.Valid?(Set.Map(ts, fn)) ensures Comparison.Valid?(ts) { + reveal cmp.Valid?(); + reveal Comparison.Valid?(); forall s0, s1 | s0 in ts && s1 in ts ensures Comparison.Complete??(s0, s1) { assert cmp.Complete??(fn(s0), fn(s1)); } @@ -756,6 +771,10 @@ module Utils.Lib.Sort.DerivedComparison { requires forall i, j | fn(i) == fn(j) :: i == j ensures Comparison.Total?(ts) { + reveal cmp.Valid?(); + reveal cmp.Total?(); + reveal Comparison.Valid?(); + reveal Comparison.Total?(); Valid(ts); forall s0, s1 | s0 in ts && s1 in ts ensures Comparison.Antisymmetric??(s0, s1) { calc ==> { From 0461afb3888be2ebb9f1cedab08edda34279abf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Wed, 24 Aug 2022 16:34:19 -0700 Subject: [PATCH 032/105] ast: Speed up verification by disabling induction --- src/AST/Entities.dfy | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/src/AST/Entities.dfy b/src/AST/Entities.dfy index 0c01a6e6..05b998f1 100644 --- a/src/AST/Entities.dfy +++ b/src/AST/Entities.dfy @@ -308,7 +308,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities set prefix <- prefixes, name <- TransitiveMembers(prefix) :: name } - lemma TransitiveMembersOfMany_Le(root: Name, members: seq) + lemma {:induction false} TransitiveMembersOfMany_Le(root: Name, members: seq) requires forall member <- members :: member.ChildOf(root) ensures TransitiveMembersOfMany(members) <= TransitiveMembers(root); { @@ -323,7 +323,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities } } - lemma TransitiveMembersOfMany_Lt(root: Name, members: seq) + lemma {:induction false} TransitiveMembersOfMany_Lt(root: Name, members: seq) requires Contains(root) requires forall member <- members :: member.ChildOf(root) ensures TransitiveMembersOfMany(members) < TransitiveMembers(root); @@ -399,7 +399,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities set name <- entities | name.StrictExtensionOf(root) } - lemma TransitiveMembers_StrictTransitiveMembers(root: Name) + lemma {:induction false} TransitiveMembers_StrictTransitiveMembers(root: Name) requires Contains(root) ensures TransitiveMembers(root) == {root} + StrictTransitiveMembers(root) { @@ -413,7 +413,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities set member <- Members(root), name <- TransitiveMembers(member) :: name } - lemma StrictTransitiveMembers_Partition(root: Name) + lemma {:induction false} StrictTransitiveMembers_Partition(root: Name) requires Valid?() requires Contains(root) ensures StrictTransitiveMembers(root) == StrictTransitiveMembers_Partitioned(root) @@ -465,20 +465,6 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities RecursiveTransitiveMembers(member) } - lemma RecursiveTransitiveMembers_Extension(root: Name) - requires Valid?() - requires Contains(root) - ensures forall d <- RecursiveTransitiveMembers(root) :: d.ExtensionOf(root) - { - forall d <- RecursiveTransitiveMembers(root) - ensures d.ExtensionOf(root) - { - RecursiveTransitiveMembers_Eq(root); - assert d in TransitiveMembers(root); - reveal TransitiveMembers(); - } - } - lemma {:induction false} RecursiveTransitiveMembers_Le(root: Name, name: Name) requires Valid?() requires Contains(root) @@ -532,6 +518,21 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities } } } + + lemma {:induction false} RecursiveTransitiveMembers_Extension(root: Name) + requires Valid?() + requires Contains(root) + ensures forall d <- RecursiveTransitiveMembers(root) :: d.ExtensionOf(root) + { + forall d <- RecursiveTransitiveMembers(root) + ensures d.ExtensionOf(root) + { + RecursiveTransitiveMembers_Eq(root); + assert d in TransitiveMembers(root); + reveal TransitiveMembers(); + } + } + } type Program = p: Program_ | p.Valid?() witness Program.EMPTY() From aa73d4dc6be21f0b9a32500d18be4eb88327d2fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Wed, 24 Aug 2022 16:34:34 -0700 Subject: [PATCH 033/105] ast: Complete proof of `Registry.Validate()` --- src/AST/Entities.dfy | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/AST/Entities.dfy b/src/AST/Entities.dfy index 05b998f1..a38d7b3d 100644 --- a/src/AST/Entities.dfy +++ b/src/AST/Entities.dfy @@ -213,13 +213,22 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities ) } - function {:opaque} Validate(): (o: Outcome>) - ensures o.Pass? <==> Valid?() + function {:opaque} Validate(): (os: Outcome>) + ensures os.Pass? <==> Valid?() { - OS.CombineSeq( - Seq.Map(name requires name in entities => ValidateEntry(name, entities[name]), - SetSort.Sort(entities.Keys, Name.Comparison)) - ) + var names := SortedNames(); + var validate := nm requires Contains(nm) => ValidateEntry(nm, entities[nm]); + var os := OS.CombineSeq(Seq.Map(validate, names)); + calc <==> { + os.Pass?; + forall o <- Seq.Map(validate, names) :: o.Pass?; + { Seq.Map_in(validate, names); } + forall name <- names :: validate(name).Pass?; + forall name <- Set.OfSeq(names) :: ValidEntry??(name, entities[name]); + { reveal AllNames(); } + Valid?(); + } + os } /// Core API From ec356cd6fb67e9b8bad89bddec5cf5600bfe2e59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Wed, 24 Aug 2022 16:35:04 -0700 Subject: [PATCH 034/105] ; ast: Remove comment made obsolete by c5ac992 --- src/AST/Entities.dfy | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/AST/Entities.dfy b/src/AST/Entities.dfy index a38d7b3d..9b0d5a56 100644 --- a/src/AST/Entities.dfy +++ b/src/AST/Entities.dfy @@ -291,9 +291,6 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities Registry(map name | name in entities :: f(entities[name])) } - // TODO Make a variant of SortedNames that traverses from the root and prove - // that it returns all entities. - function {:opaque} AllNames(): set ensures forall name :: name in AllNames() <==> Contains(name) { From 7b5b50153dfe01b13bfa060afbeeac5b6976e724 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Wed, 24 Aug 2022 17:49:52 -0700 Subject: [PATCH 035/105] ast: Complete proof of `Registry.SortedNames` --- src/AST/Entities.dfy | 7 +++++-- src/Utils/Library.dfy | 7 +++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/AST/Entities.dfy b/src/AST/Entities.dfy index 9b0d5a56..eb99dfbb 100644 --- a/src/AST/Entities.dfy +++ b/src/AST/Entities.dfy @@ -298,9 +298,12 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities } function {:opaque} SortedNames(): (all_names: seq) - ensures (set name <- all_names) == entities.Keys + ensures Set.OfSeq(all_names) == AllNames() + ensures forall name :: name in all_names <==> Contains(name) { - SetSort.Sort(entities.Keys, Name.Comparison) + Name.Total(AllNames()); + Name.Comparison.TotalValid(AllNames()); + SetSort.Sort(AllNames(), Name.Comparison) } /// Unordered traversals diff --git a/src/Utils/Library.dfy b/src/Utils/Library.dfy index 0e2224dd..33ca7a67 100644 --- a/src/Utils/Library.dfy +++ b/src/Utils/Library.dfy @@ -714,6 +714,13 @@ module Utils.Lib.Sort.Comparison { Complete?(ts) && Antisymmetric?(ts) && Transitive?(ts) } + lemma TotalValid(ts: set) + ensures Total?(ts) ==> Valid?(ts) + { + reveal Total?(); + reveal Valid?(); + } + predicate Sorted(sq: seq) { forall i, j | 0 <= i < j < |sq| :: cmp(sq[i], sq[j]).Le? } From e6475c95efa328fed47e88c579e720d0d620fab6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Wed, 24 Aug 2022 18:06:21 -0700 Subject: [PATCH 036/105] ast: Finish proof of `Registry.Validate` --- src/AST/Entities.dfy | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/AST/Entities.dfy b/src/AST/Entities.dfy index eb99dfbb..294dfd15 100644 --- a/src/AST/Entities.dfy +++ b/src/AST/Entities.dfy @@ -199,18 +199,20 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities /// Post-construction validation /// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - function {:opaque} ValidateEntry(name: Name, entity: Entity): (o: Outcome>) + function {:opaque} ValidateEntry(name: Name, entity: Entity): (os: Outcome>) requires Lookup(name) == Some(entity) - ensures o.Pass? <==> ValidEntry??(name, entity) + ensures os.Pass? <==> ValidEntry??(name, entity) { - assume false; // TODO - OS.Combine( - [if ValidName??(name, entity) then Pass else Fail(NameMismatch(name, entity.ei.name)), - if ValidParent??(name) then Pass else Fail(UnboundParent(name, name.parent))] - + Seq.Map(m => - if m in entities then Pass else Fail(UnboundMember(name, m)), - entity.ei.members) - ) + var validName := Need(ValidName??(name, entity), NameMismatch(name, entity.ei.name)); + var validParent := if ValidParent??(name) then Pass else Fail(UnboundParent(name, name.parent)); + var validMembers := Seq.Map(m => Need(m in entities, UnboundMember(name, m)), entity.ei.members); + var os := OS.Combine([validName, validParent] + validMembers); + calc <==> { + os.Pass?; + validName.Pass? && validParent.Pass? && forall o <- validMembers :: o.Pass?; + validName.Pass? && validParent.Pass? && forall m <- entity.ei.members :: m in entities; + } + os } function {:opaque} Validate(): (os: Outcome>) From 950cfee6c88c11b17ac0f0c0e8021a736cff1ff1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Wed, 24 Aug 2022 18:48:13 -0700 Subject: [PATCH 037/105] ast: Rename `Registry.Add` to `AddRoot` and implement `AddMember` --- src/AST/Entities.dfy | 44 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/src/AST/Entities.dfy b/src/AST/Entities.dfy index 294dfd15..4d8edfc6 100644 --- a/src/AST/Entities.dfy +++ b/src/AST/Entities.dfy @@ -166,7 +166,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities /// Well-formedness /// ~~~~~~~~~~~~~~~ - predicate ValidName??(name: Name, entity: Entity) { + static predicate ValidName??(name: Name, entity: Entity) { entity.ei.name == name } @@ -280,13 +280,45 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities Get(name).ei.members } - function Add(name: Name, entity: Entity): Registry + function AddRoot(name: Name, entity: Entity): Registry requires Valid?() + requires name.Anonymous? requires !Contains(name) - requires entity.Module? - requires ValidEntry??(name, entity) // TODO: Only allows adding "Anonymous" + requires entity.ei.members == [] + requires entity.ei.name == name { - this.(entities := entities[name := entity]) + this.(entities := entities[Anonymous := entity]) + } + + function AddMember(name: Name, entity: Entity): Registry + requires Valid?() + requires name.Name? + requires !Contains(name) + requires entity.ei.members == [] + requires ValidName??(name, entity) + requires Contains(name.parent) + { + var parent := Get(name.parent); + var parent := parent.(ei := parent.ei.(members := parent.ei.members + [name])); + var entities := entities[name := entity][name.parent := parent]; + var this': Registry_ := this.(entities := entities); + assert entities.Keys == this.entities.Keys + {name}; + assert this'.Valid?() by { + forall nm <- this'.entities ensures this'.ValidEntry??(nm, entities[nm]) { + if nm != name && nm != name.parent {} + } + } + this' + } + + function Add(entity: Entity): Registry + requires Valid?() + requires !Contains(entity.ei.name) + requires entity.ei.members == [] + requires entity.ei.name.Name? ==> Contains(entity.ei.name.parent) + { + var name := entity.ei.name; + if name.Anonymous? then AddRoot(name, entity) else AddMember(name, entity) } function Map(f: EntityTransformer): Registry requires Valid?() { @@ -554,7 +586,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities { static function EMPTY(): (p: Program_) ensures p.Valid?() { Program( - Registry.EMPTY().Add(Anonymous, Entity.Module(EntityInfo.Mk(Anonymous), Module.Module())), + Registry.EMPTY().Add(Entity.Module(EntityInfo.Mk(Anonymous), Module.Module())), defaultModule := Anonymous, mainMethod := None ) From 4fb886bdae5d8ffaecef7202cd777c0057e71059 Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Wed, 24 Aug 2022 11:40:29 -0700 Subject: [PATCH 038/105] interop: Add simple dictionary-to-list code --- src/Interop/CSharpInterop.cs | 4 ++++ src/Interop/CSharpInterop.dfy | 2 ++ 2 files changed, 6 insertions(+) diff --git a/src/Interop/CSharpInterop.cs b/src/Interop/CSharpInterop.cs index a4f81081..a909634f 100644 --- a/src/Interop/CSharpInterop.cs +++ b/src/Interop/CSharpInterop.cs @@ -19,5 +19,9 @@ public static B FoldR(Func f, B b0, List lA) { } return b0; } + + public static List DictionaryToList(Dictionary d) { + return d.ToList(); + } } } diff --git a/src/Interop/CSharpInterop.dfy b/src/Interop/CSharpInterop.dfy index 94162262..46c556cd 100644 --- a/src/Interop/CSharpInterop.dfy +++ b/src/Interop/CSharpInterop.dfy @@ -12,6 +12,8 @@ module {:extern "CSharpInterop"} Bootstrap.Interop.CSharpInterop { static method {:extern} Mk() returns (l: List) static method {:extern} Append(l: List, t: T) + static function method {:extern} DictionaryToList(d: Dictionary): List<(K, V)> + static function method ToSeq(l: List) : seq { FoldR((t, s) => [t] + s, [], l) } From eb98c72c3138065ca759739bc289fdc09f348824 Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Wed, 24 Aug 2022 11:41:09 -0700 Subject: [PATCH 039/105] ast: Add some entity translations Some of them still return Failure(Invalid("NYI")) --- src/AST/Entities.dfy | 4 +- src/AST/Predicates.dfy | 4 + src/AST/Translator.Expressions.dfy | 176 ++++++++++++++++++++++++++ src/Interop/CSharpDafnyASTInterop.dfy | 2 + src/Utils/Library.dfy | 20 +++ 5 files changed, 204 insertions(+), 2 deletions(-) diff --git a/src/AST/Entities.dfy b/src/AST/Entities.dfy index 4d8edfc6..acb13504 100644 --- a/src/AST/Entities.dfy +++ b/src/AST/Entities.dfy @@ -19,7 +19,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities // DISCUSS: Should this module be parameterized by `TExpr`? datatype Module = - Module() + Module(members: seq) datatype ExportSet = ExportSet(provided: set, revealed: set) @@ -586,7 +586,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities { static function EMPTY(): (p: Program_) ensures p.Valid?() { Program( - Registry.EMPTY().Add(Entity.Module(EntityInfo.Mk(Anonymous), Module.Module())), + Registry.EMPTY().Add(Entity.Module(EntityInfo.Mk(Anonymous), Module.Module([]))), defaultModule := Anonymous, mainMethod := None ) diff --git a/src/AST/Predicates.dfy b/src/AST/Predicates.dfy index 69cae7dd..fdb96c3a 100644 --- a/src/AST/Predicates.dfy +++ b/src/AST/Predicates.dfy @@ -5,6 +5,7 @@ module Shallow { import opened Utils.Lib import opened Syntax + /* function method All_Method(m: Method, P: Expr -> bool) : bool { match m { case Method(CompileName_, methodBody) => P(methodBody) @@ -20,6 +21,7 @@ module Shallow { function method All(p: Program, P: Expr -> bool) : bool { All_Program(p, P) } + */ } module DeepImpl { @@ -39,6 +41,7 @@ abstract module Base { : (b: bool) decreases e.Depth(), 1 + /* function method All_Method(m: Method, P: Expr -> bool) : bool { Shallow.All_Method(m, e => All_Expr(e, P)) } @@ -46,6 +49,7 @@ abstract module Base { function method All_Program(p: Program, P: Expr -> bool) : bool { Shallow.All_Program(p, e => All_Expr(e, P)) } + */ // // Lemmas diff --git a/src/AST/Translator.Expressions.dfy b/src/AST/Translator.Expressions.dfy index 3149aac3..f7f9f97b 100644 --- a/src/AST/Translator.Expressions.dfy +++ b/src/AST/Translator.Expressions.dfy @@ -3,6 +3,7 @@ include "../Interop/CSharpInterop.dfy" include "../Interop/CSharpDafnyInterop.dfy" include "../Interop/CSharpDafnyASTInterop.dfy" include "../Utils/Library.dfy" +include "Entities.dfy" include "Syntax.dfy" include "Predicates.dfy" include "Translator.Common.dfy" @@ -19,6 +20,8 @@ module Bootstrap.AST.Translator { import D = Syntax import DE = Syntax.Exprs import DT = Syntax.Types + import E = Entities + import N = Names import P = Predicates.Deep import opened Common @@ -533,6 +536,7 @@ module Bootstrap.AST.Translator { else Failure(UnsupportedStmt(s)) } + /* function method TranslateMethod(m: C.Method) : TranslationResult reads * @@ -550,4 +554,176 @@ module Bootstrap.AST.Translator { var tm :- TranslateMethod(p.MainMethod); Success(D.Program(tm)) } + */ + + function method TranslateName(str: System.String): N.Name { + var name := TypeConv.AsString(str); + var parts := Seq.Split('.', name); + assume forall s | s in parts :: s != ""; + assert forall s | s in parts :: '.' !in s; + assert forall s | s in parts :: s != "" && '.' !in s; + var atoms : seq := parts; + Seq.FoldL((n: N.Name, a: N.Atom) => N.Name(n, a), N.Anonymous, atoms) + } + + function method TranslateImports(md: C.ModuleDecl): (imps: TranslationResult>) + reads * + { + Failure(Invalid("NYI")) + } + + function method TranslateExportSet(md: C.ModuleDecl): (exps: TranslationResult) + reads * + { + Failure(Invalid("NYI")) + } + + function method TranslateField(f: C.Field): (d: TranslationResult) + reads * + { + var kind := if f.IsMutable then E.Var else E.Const; + var name := f.FullDafnyName; + Failure(Invalid("NYI")) + } + + function method TranslateMethod(m: C.Method): (d: TranslationResult) + reads * + { + var body :- TranslateStatement(m.Body); + var def := if m is C.Constructor then + E.Constructor(body) + else + E.Method(body); + Success(E.Callable(def)) + } + + function method TranslateFunction(f: C.Function): (d: TranslationResult) + reads * + { + var body :- TranslateExpression(f.Body); + Success(E.Callable(E.Function(body))) + } + + function method TranslateMemberDecl(md: C.MemberDecl): (d: TranslationResult) + reads * + { + if md is C.Field then + TranslateField(md) + else if md is C.Function then + TranslateFunction(md) + else if md is C.Method then + TranslateMethod(md) + else + Failure(Invalid("Unsupported member declaration type")) + } + + function method TranslateValueTypeDecl(vt: C.ValuetypeDecl): (e: TranslationResult>) + reads * + { + Failure(Invalid("NYI")) + } + + function method TranslateTypeSynonymDecl(ts: C.TypeSynonymDecl): (e: TranslationResult>) + reads * + { + Failure(Invalid("NYI")) + } + + function method TranslateOpaqueTypeDecl(ot: C.OpaqueTypeDecl): (e: TranslationResult>) + reads * + { + Failure(Invalid("NYI")) + } + + function method TranslateNewtypeDecl(nt: C.NewtypeDecl): (e: TranslationResult>) + reads * + { + Failure(Invalid("NYI")) + } + + function method TranslateDatatypeDecl(dt: C.DatatypeDecl): (e: TranslationResult>) + reads * + { + Failure(Invalid("NYI")) + } + + function method TranslateTraitDecl(t: C.TraitDecl): (e: TranslationResult>) + reads * + { + Failure(Invalid("NYI")) + } + + function method TranslateClassDecl(c: C.ClassDecl): (e: TranslationResult>) + reads * + { + Failure(Invalid("NYI")) + } + + function method TranslateTopLevelDeclWithMembers(tl: C.TopLevelDeclWithMembers): (e: TranslationResult>) + reads * + { + if tl is C.OpaqueTypeDecl then + TranslateOpaqueTypeDecl(tl) + else if tl is C.NewtypeDecl then + TranslateNewtypeDecl(tl) + else if tl is C.DatatypeDecl then + TranslateDatatypeDecl(tl) + else if tl is C.TraitDecl then + TranslateTraitDecl(tl) + else if tl is C.ClassDecl then + TranslateClassDecl(tl) + else + Failure(Invalid("Unsupported top level declaration type")) + } + + function method TranslateTopLevelDecl(tl: C.TopLevelDecl): (exps: TranslationResult>) + reads * + decreases ASTHeight(tl), 0 + { + if tl is C.TopLevelDeclWithMembers then + TranslateTopLevelDeclWithMembers(tl) + else if tl is C.ValuetypeDecl then + TranslateValueTypeDecl(tl) + else if tl is C.ModuleDecl then + var md := tl as C.ModuleDecl; + assume ASTHeight(md.Signature) < ASTHeight(tl); + TranslateModule(md.Signature) + else + Failure(Invalid("Unsupported top level declaration type")) + } + + function method TranslateModule(sig: C.ModuleSignature): (m: TranslationResult>) + reads * + decreases ASTHeight(sig), 1 + { + var name := TranslateName(sig.ModuleDef.FullName); + var attrs := sig.ModuleDef.Attributes; + var includes := ListUtils.ToSeq(sig.ModuleDef.Includes); + var exports := sig.ExportSets; + var topLevels := ListUtils.ToSeq(ListUtils.DictionaryToList(sig.TopLevels)); + var topDecls :- Seq.MapResult(topLevels, + (tl: (System.String, C.TopLevelDecl)) reads * => + assume ASTHeight(tl.1) < ASTHeight(sig); + TranslateTopLevelDecl(tl.1)); + var topDecls' := Seq.Flatten(topDecls); + var ei := E.EntityInfo.Mk(name); + var topNames := Seq.Map((e: E.Entity) => e.ei.name, topDecls'); + var mod := E.Entity.Module(ei, E.Module.Module(topNames)); + Success([mod] + topDecls') + } + + function method TranslateProgram(p: C.Program): (exps: TranslationResult) + reads * + { + var moduleSigs := ListUtils.ToSeq(ListUtils.DictionaryToList(p.ModuleSigs)); + var entities :- Seq.MapResult(moduleSigs, + (sig: (C.ModuleDefinition, C.ModuleSignature)) reads * => TranslateModule(sig.1)); + var regMap := Seq.FoldL((m:map, e: E.Entity) => m + map[e.ei.name := e], map[], Seq.Flatten(entities)); + var mainMethodName := TranslateName(p.MainMethod.FullName); + var defaultModuleName := TranslateName(p.DefaultModule.FullName); + var reg := E.Registry_.Registry(regMap); + :- Need(reg.Validate().Pass?, Invalid("Failed to validate registry")); + var prog := E.Program(reg, defaultModule := defaultModuleName, mainMethod := Some(mainMethodName)); + if prog.Valid?() then Success(prog) else Failure(Invalid("Generated invalid program")) + } } diff --git a/src/Interop/CSharpDafnyASTInterop.dfy b/src/Interop/CSharpDafnyASTInterop.dfy index 2a42c527..f73dc5a5 100644 --- a/src/Interop/CSharpDafnyASTInterop.dfy +++ b/src/Interop/CSharpDafnyASTInterop.dfy @@ -8,6 +8,8 @@ module {:extern "CSharpDafnyASTInterop"} Bootstrap.Interop.CSharpDafnyASTInterop function {:axiom} ASTHeight(c: object) : nat requires || c is CSharpDafnyASTModel.Expression || c is CSharpDafnyASTModel.Statement + || c is CSharpDafnyASTModel.Declaration + || c is CSharpDafnyASTModel.ModuleSignature class {:extern} TypeUtils { constructor {:extern} () requires false // Prevent instantiation diff --git a/src/Utils/Library.dfy b/src/Utils/Library.dfy index 33ca7a67..3cd85f40 100644 --- a/src/Utils/Library.dfy +++ b/src/Utils/Library.dfy @@ -287,6 +287,26 @@ module Utils.Lib.Seq { case Some(y) => [y] case None => []) + MapFilter(s[1..], f) } + + function method Break(f: T -> bool, s: seq): (result: (seq, seq)) + ensures |result.1| <= |s| + ensures forall c | c in result.0 :: !f(c) + { + if |s| == 0 then ([], []) + else if f(s[0]) then ([], s) + else + var (h, t) := Break(f, s[1..]); + ([s[0]] + h, t) + } + + function method Split(x: T, s: seq): (result: seq>) + decreases |s| + ensures forall s | s in result :: x !in s + { + var (h, t) := Break(y => y == x, s); + assert !(x in h); + [h] + if |t| == 0 then [] else Split(x, t[1..]) + } } module Utils.Lib.Outcome.OfSeq { // FIXME rename to Seq From bb64ebfc9c44ef8adac7b5dc7e8bc743f9a689fb Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Wed, 24 Aug 2022 11:41:51 -0700 Subject: [PATCH 040/105] tools: Add initial auditor, using the entity model --- src/Tools/Auditor/Auditor.dfy | 76 ++++++++++++++ src/Tools/Auditor/AuditorEntryPoint.cs | 11 +++ src/Tools/Auditor/Report.dfy | 131 +++++++++++++++++++++++++ src/Tools/Auditor/ReportTest.dfy | 33 +++++++ 4 files changed, 251 insertions(+) create mode 100644 src/Tools/Auditor/Auditor.dfy create mode 100644 src/Tools/Auditor/AuditorEntryPoint.cs create mode 100644 src/Tools/Auditor/Report.dfy create mode 100644 src/Tools/Auditor/ReportTest.dfy diff --git a/src/Tools/Auditor/Auditor.dfy b/src/Tools/Auditor/Auditor.dfy new file mode 100644 index 00000000..7bc6b496 --- /dev/null +++ b/src/Tools/Auditor/Auditor.dfy @@ -0,0 +1,76 @@ +include "../../AST/Entities.dfy" +include "../../AST/Names.dfy" +include "../../AST/Syntax.dfy" +include "../../AST/Translator.Entities.dfy" +include "../../Interop/CSharpDafnyASTModel.dfy" +include "../../Interop/CSharpDafnyInterop.dfy" +include "../../Utils/Library.dfy" +include "Report.dfy" + +import opened Bootstrap.AST.Entities +import opened Bootstrap.AST.Names +import opened Bootstrap.AST.Syntax.Exprs +import opened Bootstrap.AST.EntityTranslator +import opened AuditReport +import opened Utils.Lib.Datatypes +import opened Utils.Lib.Seq + +//// AST traversals //// + +// TODO: can't be implemented yet because there's no representation for `assume` +//predicate ContainsAssumeStatement(e: Expr) + +//// Tag extraction and processing //// + +function TagIf(cond: bool, t: Tag): set { + if cond then {t} else {} +} + +function GetTags(e: Entity): set { + TagIf(exists a | a in e.ei.attrs :: a.name == Extern, HasExternAttribute) + + TagIf(exists a | a in e.ei.attrs :: a.name == Axiom, HasAxiomAttribute) + + TagIf(e.Type? && e.t.SubsetType? && e.t.st.witnessExpr.None?, HasNoWitness) + + TagIf(e.Definition? && e.d.Callable? /*&& e.d.ci.body.None?*/, HasNoBody) + + //TagIf(e.Definition? && e.d.Callable? && ContainsAssumeStatement(e.d.ci.body), HasNoBody) + + TagIf(e.Definition? && e.d.Callable? /*&& e.d.ci.ensures.Some?*/, HasEnsuresClause) +} + +//// Rport generation //// + +function AddAssumptions(e: Entity, rpt: Report): Report { + var tags := GetTags(e); + if IsAssumption(tags) + then Report(rpt.assumptions + [Assumption(e.ei.name.ToString(), tags)]) + else rpt +} + +function FoldEntities(f: (Entity, T) -> T, reg: Registry, init: T): T { + var names := reg.SortedNames(); + FoldL((a, n) requires reg.Contains(n) => f(reg.Get(n), a), init, names) +} + +function GenerateAuditReport(reg: Registry): Report { + FoldEntities(AddAssumptions, reg, EmptyReport) +} + +function {:export} Audit(p: Bootstrap.Interop.CSharpDafnyASTModel.Program): string + reads * +{ + var res := TranslateProgram(p); + match res { + case Success(p') => + var rpt := GenerateAuditReport(p'.registry); + RenderAuditReportMarkdown(rpt) + case Failure(err) => err.ToString() + } +} + +//// Later functionality //// + +/* +predicate EntityDependsOn(client: Entity, target: Entity) + +function ImmediateDependents(e: Entity): set + +function TransitiveDependents(e: Entity): set +*/ diff --git a/src/Tools/Auditor/AuditorEntryPoint.cs b/src/Tools/Auditor/AuditorEntryPoint.cs new file mode 100644 index 00000000..4c747943 --- /dev/null +++ b/src/Tools/Auditor/AuditorEntryPoint.cs @@ -0,0 +1,11 @@ +namespace Microsoft.Dafny; + +public class AuditorPlugin : IRewriter { + private readonly DafnyAuditor auditor = new(); + + internal override void PostResolve(Program program) { + var text = auditor.Audit(program); + // TODO: write to the console or file depending on options + Console.WriteLine(text); + } +} diff --git a/src/Tools/Auditor/Report.dfy b/src/Tools/Auditor/Report.dfy new file mode 100644 index 00000000..94e14ac1 --- /dev/null +++ b/src/Tools/Auditor/Report.dfy @@ -0,0 +1,131 @@ +include "../../Utils/Library.dfy" + +module AuditReport { + import opened Utils.Lib.Seq + + //// Data types for report //// + + datatype Tag = + | IsGhost + | IsSubsetType + | IsCallable + | HasNoBody // TODO: should all tags be positive? + | HasAxiomAttribute + | HasExternAttribute + | HasVerifyFalseAttribute + | HasAssumeInBody // Maybe: (expr: Expression) TODO: :axiom? + | HasRequiresClause + | HasEnsuresClause + | HasNoWitness // TODO: should all tags be positive? + | HasJustification + // TODO: decreases *? + { + function method ToString(): string { + match this { + case IsGhost => "IsGhost" + case IsSubsetType => "IsSubsetType" + case IsCallable => "IsCallable" + case HasNoBody => "HasNoBody" + case HasAxiomAttribute => "HasAxiomAttribute" + case HasExternAttribute => "HasExternAttribute" + case HasVerifyFalseAttribute => "HasVerifyFalseAttribute" + case HasAssumeInBody => "HasAssumeInBody" + case HasRequiresClause => "HasRequiresClause" + case HasEnsuresClause => "HasEnsuresClause" + case HasNoWitness => "HasNoWitness" + case HasJustification => "HasJustification" + } + } + } + + datatype Assumption = + Assumption(name: string, tags: set) + + datatype Report = + Report(assumptions: seq) + + const EmptyReport := Report([]) + + //// Tag categorization //// + + predicate IsAssumption(ts: set) { + || (IsSubsetType in ts && HasNoWitness in ts) + || HasAxiomAttribute in ts + || (&& IsCallable in ts + && (|| (HasEnsuresClause in ts && (HasNoBody in ts || HasExternAttribute in ts)) + || HasAssumeInBody in ts)) + } + + predicate method IsExplicitAssumption(ts: set) { + HasAxiomAttribute in ts || HasAssumeInBody in ts + } + + //// Report rendering //// + + function method BoolYN(b: bool): string { + if b then "Y" else "N" + } + + function method MaybeElt(b: bool, elt: T): seq { + if b then [elt] else [] + } + + function method Interleave(t:T, ts: seq): seq { + if |ts| == 0 then [] + else if |ts| == 1 then ts + else [ts[0], t] + Interleave(t, ts[1..]) + } + + // TODO: improve these descriptions + function method AssumptionDescription(ts: set): seq<(string, string)> { + MaybeElt(IsCallable in ts && HasNoBody in ts && IsGhost in ts, + ("Function or lemma has no body.", + "Provide a body.")) + + MaybeElt(IsCallable in ts && HasNoBody in ts && !(IsGhost in ts), + ("Callable definition has no body.", + "Provide a body.")) + + MaybeElt(HasExternAttribute in ts && HasRequiresClause in ts, + ("Extern symbol with precondition.", + "Extensively test client code.")) + + MaybeElt(HasExternAttribute in ts && HasEnsuresClause in ts, + ("Extern symbol with postcondition.", + "Provide a model or a test case, or both.")) + + MaybeElt(IsSubsetType in ts && HasNoWitness in ts, + ("Subset type has no witness and could be empty.", + "Provide a witness.")) + + MaybeElt(HasAxiomAttribute in ts, + ("Has explicit `{:axiom}` attribute.", + "Attempt to provide a proof or model.")) + + MaybeElt(HasAssumeInBody in ts, + ("Has `assume` statement in body.", + "Try to replace with `assert` and prove.")) + } + + lemma AllAssumptionsDescribed(ts: set) + requires IsAssumption(ts) + ensures |AssumptionDescription(ts)| > 0 + { + } + + function method RenderAssumptionMarkdown(a: Assumption): string { + var descs := AssumptionDescription(a.tags); + var issues := Map( (desc: (string, string)) => desc.0, descs); + var mitigations := Map( (desc: (string, string)) => desc.1, descs); + var cells := + [ a.name + , BoolYN(!(IsGhost in a.tags)) + , BoolYN(IsExplicitAssumption(a.tags)) + , BoolYN(HasExternAttribute in a.tags) + , Flatten(Interleave("
", issues)) + , Flatten(Interleave("
", mitigations)) + ]; + "| " + Flatten(Interleave(" | ", cells)) + " |" + } + + function method RenderAuditReportMarkdown(r: Report): string { + var header := + "|Name|Compiled|Explicit Assumption|Extern|Issue|Mitigation|\n" + + "|----|--------|-------------------|------|-----|----------|\n"; + FoldL((s, a) => s + RenderAssumptionMarkdown(a) + "\n", header, r.assumptions) + } +} \ No newline at end of file diff --git a/src/Tools/Auditor/ReportTest.dfy b/src/Tools/Auditor/ReportTest.dfy new file mode 100644 index 00000000..7dff6858 --- /dev/null +++ b/src/Tools/Auditor/ReportTest.dfy @@ -0,0 +1,33 @@ +include "Report.dfy" + +module AuditReportTest { + + import opened AuditReport + + method Main() { + var rpt := Report([ + Assumption("MinusBv8NoBody", + {IsCallable, IsGhost, HasNoBody, HasEnsuresClause}), + Assumption("LeftShiftBV128", + {IsCallable, IsGhost, HasNoBody, HasEnsuresClause, HasAxiomAttribute}), + Assumption("MinusBv8Assume", + {IsCallable, IsGhost, HasEnsuresClause, HasAssumeInBody}), + Assumption("GenerateBytes", + {IsCallable, HasExternAttribute, HasEnsuresClause, HasNoBody}), + Assumption("GenerateBytesWithModel", + {IsCallable, HasExternAttribute, HasEnsuresClause}), + Assumption("GenerateBytesWrapper", + {IsCallable, HasExternAttribute, HasEnsuresClause, HasAssumeInBody}), + Assumption("emptyType", + {IsSubsetType, HasNoWitness}) + // This doesn't pass IsAsssumption + /* + Assumption("WhoKnows", + {IsCallable, IsGhost, HasNoBody}) + */ + ]); + assert forall a | a in rpt.assumptions :: IsAssumption(a.tags); + var text := RenderAuditReportMarkdown(rpt); + print text; + } +} From 34534016008b22fd7d856d751347e2cc1131c95d Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Thu, 25 Aug 2022 14:33:49 -0700 Subject: [PATCH 041/105] interop: Replace `DictionaryToList` with `DictUtils.Fold` --- src/AST/Translator.Expressions.dfy | 4 ++-- src/Interop/CSharpInterop.cs | 9 +++++++-- src/Interop/CSharpInterop.dfy | 15 +++++++++++++-- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/AST/Translator.Expressions.dfy b/src/AST/Translator.Expressions.dfy index f7f9f97b..1572c96a 100644 --- a/src/AST/Translator.Expressions.dfy +++ b/src/AST/Translator.Expressions.dfy @@ -700,7 +700,7 @@ module Bootstrap.AST.Translator { var attrs := sig.ModuleDef.Attributes; var includes := ListUtils.ToSeq(sig.ModuleDef.Includes); var exports := sig.ExportSets; - var topLevels := ListUtils.ToSeq(ListUtils.DictionaryToList(sig.TopLevels)); + var topLevels := DictUtils.DictionaryToSeq(sig.TopLevels); var topDecls :- Seq.MapResult(topLevels, (tl: (System.String, C.TopLevelDecl)) reads * => assume ASTHeight(tl.1) < ASTHeight(sig); @@ -715,7 +715,7 @@ module Bootstrap.AST.Translator { function method TranslateProgram(p: C.Program): (exps: TranslationResult) reads * { - var moduleSigs := ListUtils.ToSeq(ListUtils.DictionaryToList(p.ModuleSigs)); + var moduleSigs := DictUtils.DictionaryToSeq(p.ModuleSigs); var entities :- Seq.MapResult(moduleSigs, (sig: (C.ModuleDefinition, C.ModuleSignature)) reads * => TranslateModule(sig.1)); var regMap := Seq.FoldL((m:map, e: E.Entity) => m + map[e.ei.name := e], map[], Seq.Flatten(entities)); diff --git a/src/Interop/CSharpInterop.cs b/src/Interop/CSharpInterop.cs index a909634f..9030ce6e 100644 --- a/src/Interop/CSharpInterop.cs +++ b/src/Interop/CSharpInterop.cs @@ -19,9 +19,14 @@ public static B FoldR(Func f, B b0, List
lA) { } return b0; } + } - public static List DictionaryToList(Dictionary d) { - return d.ToList(); + public class DictUtils { + public static R FoldL(Func f, R r0, Dictionary d) { + foreach (var k in d.Keys) { + r0 = f(r0, (k, d[k])); + } + return r0; } } } diff --git a/src/Interop/CSharpInterop.dfy b/src/Interop/CSharpInterop.dfy index 46c556cd..18020794 100644 --- a/src/Interop/CSharpInterop.dfy +++ b/src/Interop/CSharpInterop.dfy @@ -12,8 +12,6 @@ module {:extern "CSharpInterop"} Bootstrap.Interop.CSharpInterop { static method {:extern} Mk() returns (l: List) static method {:extern} Append(l: List, t: T) - static function method {:extern} DictionaryToList(d: Dictionary): List<(K, V)> - static function method ToSeq(l: List) : seq { FoldR((t, s) => [t] + s, [], l) } @@ -26,4 +24,17 @@ module {:extern "CSharpInterop"} Bootstrap.Interop.CSharpInterop { } } } + + class DictUtils { + constructor {:extern} () requires false // Prevent instantiation + + static function method {:extern} FoldL(f: (R, (K, V)) -> R, r0: R, d: Dictionary): R + + static function method ReduceSeq(acc: seq<(K, V)>, entry: (K, V)): seq<(K, V)> { + acc + [entry] + } + static function method DictionaryToSeq(d: Dictionary): seq<(K, V)> { + FoldL(ReduceSeq, [], d) + } + } } From e59d13c7fe5deef09c4ecf71a32a641795a32614 Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Thu, 25 Aug 2022 14:35:11 -0700 Subject: [PATCH 042/105] ast: Remove module member name sequence --- src/AST/Entities.dfy | 4 ++-- src/AST/Translator.Expressions.dfy | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/AST/Entities.dfy b/src/AST/Entities.dfy index acb13504..4d8edfc6 100644 --- a/src/AST/Entities.dfy +++ b/src/AST/Entities.dfy @@ -19,7 +19,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities // DISCUSS: Should this module be parameterized by `TExpr`? datatype Module = - Module(members: seq) + Module() datatype ExportSet = ExportSet(provided: set, revealed: set) @@ -586,7 +586,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities { static function EMPTY(): (p: Program_) ensures p.Valid?() { Program( - Registry.EMPTY().Add(Entity.Module(EntityInfo.Mk(Anonymous), Module.Module([]))), + Registry.EMPTY().Add(Entity.Module(EntityInfo.Mk(Anonymous), Module.Module())), defaultModule := Anonymous, mainMethod := None ) diff --git a/src/AST/Translator.Expressions.dfy b/src/AST/Translator.Expressions.dfy index 1572c96a..74c34840 100644 --- a/src/AST/Translator.Expressions.dfy +++ b/src/AST/Translator.Expressions.dfy @@ -706,9 +706,8 @@ module Bootstrap.AST.Translator { assume ASTHeight(tl.1) < ASTHeight(sig); TranslateTopLevelDecl(tl.1)); var topDecls' := Seq.Flatten(topDecls); - var ei := E.EntityInfo.Mk(name); - var topNames := Seq.Map((e: E.Entity) => e.ei.name, topDecls'); - var mod := E.Entity.Module(ei, E.Module.Module(topNames)); + var ei := E.EntityInfo.Mk(name); // TODO + var mod := E.Entity.Module(ei, E.Module.Module()); Success([mod] + topDecls') } From bf15256d360b996c4acde6c091391b58fe426128 Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Thu, 25 Aug 2022 14:41:38 -0700 Subject: [PATCH 043/105] auditor: Rename entry point --- src/Tools/Auditor/{AuditorEntryPoint.cs => EntryPoint.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/Tools/Auditor/{AuditorEntryPoint.cs => EntryPoint.cs} (100%) diff --git a/src/Tools/Auditor/AuditorEntryPoint.cs b/src/Tools/Auditor/EntryPoint.cs similarity index 100% rename from src/Tools/Auditor/AuditorEntryPoint.cs rename to src/Tools/Auditor/EntryPoint.cs From d8e33da9fd07c8fc822cc4a806dfaea301516dac Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Thu, 25 Aug 2022 16:36:58 -0700 Subject: [PATCH 044/105] ast: Fill in most C# AST to Entity translations --- src/AST/Entities.dfy | 5 +- src/AST/Translator.Expressions.dfy | 141 +++++++++++++++++++---------- 2 files changed, 94 insertions(+), 52 deletions(-) diff --git a/src/AST/Entities.dfy b/src/AST/Entities.dfy index 4d8edfc6..615ea479 100644 --- a/src/AST/Entities.dfy +++ b/src/AST/Entities.dfy @@ -12,6 +12,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities { import opened Names import opened Syntax.Exprs + import ST = Syntax.Types import opened Utils.Lib.Datatypes import Utils.Lib.SetSort import OS = Utils.Lib.Outcome.OfSeq @@ -31,7 +32,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities SubsetType(boundVar: string, pred: Expr, witnessExpr: Option) datatype TypeAlias = - TypeAlias(base: Name) + TypeAlias(base: ST.Type) datatype AbstractType = AbstractType() datatype TraitType = @@ -55,7 +56,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities datatype FieldKind = Const | Var datatype Field = - Field(kind: FieldKind, body: Expr) + Field(kind: FieldKind, body: Option) datatype Callable = | Method(body: Expr) diff --git a/src/AST/Translator.Expressions.dfy b/src/AST/Translator.Expressions.dfy index 74c34840..17b32be0 100644 --- a/src/AST/Translator.Expressions.dfy +++ b/src/AST/Translator.Expressions.dfy @@ -556,55 +556,74 @@ module Bootstrap.AST.Translator { } */ - function method TranslateName(str: System.String): N.Name { + function method TranslateName(str: System.String): TranslationResult { var name := TypeConv.AsString(str); var parts := Seq.Split('.', name); - assume forall s | s in parts :: s != ""; + :- Need(forall s | s in parts :: s != "", Invalid("Empty component in name")); assert forall s | s in parts :: '.' !in s; assert forall s | s in parts :: s != "" && '.' !in s; var atoms : seq := parts; - Seq.FoldL((n: N.Name, a: N.Atom) => N.Name(n, a), N.Anonymous, atoms) + Success(Seq.FoldL((n: N.Name, a: N.Atom) => N.Name(n, a), N.Anonymous, atoms)) } - function method TranslateImports(md: C.ModuleDecl): (imps: TranslationResult>) - reads * - { - Failure(Invalid("NYI")) + function method TranslateAttributeName(s: string): E.AttributeName { + match s { + case "axiom" => E.AttributeName.Axiom + case "extern" => E.AttributeName.Extern + case _ => E.AttributeName.UserAttribute(s) + } } - function method TranslateExportSet(md: C.ModuleDecl): (exps: TranslationResult) + function method TranslateAttributes(attrs: C.Attributes): TranslationResult> reads * { - Failure(Invalid("NYI")) + var name := TypeConv.AsString(attrs.Name); + var args :- Seq.MapResult(ListUtils.ToSeq(attrs.Args), TranslateExpression); + // TODO: Attributes needs to be nullable + //var rest :- if attrs.Prev == null then Success([]) else TranslateAttributes(attrs.Prev); + var rest := []; + Success([E.Attribute.Attribute(TranslateAttributeName(name), args)] + rest) } - function method TranslateField(f: C.Field): (d: TranslationResult) + function method TranslateField(f: C.Field): (d: TranslationResult) reads * { + var name :- TranslateName(f.FullDafnyName); + var attrs :- TranslateAttributes(f.Attributes); var kind := if f.IsMutable then E.Var else E.Const; - var name := f.FullDafnyName; - Failure(Invalid("NYI")) + var ei := E.EntityInfo(name, attrs := attrs, members := []); + if f is C.ConstantField then + var init :- TranslateExpression((f as C.ConstantField).Rhs); + Success(E.Definition(ei, E.Definition.Field(E.Field.Field(kind, Some(init))))) + else + Success(E.Definition(ei, E.Definition.Field(E.Field.Field(kind, None)))) } - function method TranslateMethod(m: C.Method): (d: TranslationResult) + function method TranslateMethod(m: C.Method): (d: TranslationResult) reads * { + var name :- TranslateName(m.FullDafnyName); + var attrs :- TranslateAttributes(m.Attributes); var body :- TranslateStatement(m.Body); var def := if m is C.Constructor then E.Constructor(body) else E.Method(body); - Success(E.Callable(def)) + var ei := E.EntityInfo(name, attrs := attrs, members := []); + Success(E.Definition(ei, E.Callable(def))) } - function method TranslateFunction(f: C.Function): (d: TranslationResult) + function method TranslateFunction(f: C.Function): (d: TranslationResult) reads * { + var name :- TranslateName(f.FullDafnyName); + var attrs :- TranslateAttributes(f.Attributes); var body :- TranslateExpression(f.Body); - Success(E.Callable(E.Function(body))) + var ei := E.EntityInfo(name, attrs := attrs, members := []); + Success(E.Definition(ei, E.Callable(E.Function(body)))) } - function method TranslateMemberDecl(md: C.MemberDecl): (d: TranslationResult) + function method TranslateMemberDecl(md: C.MemberDecl): (d: TranslationResult) reads * { if md is C.Field then @@ -617,63 +636,85 @@ module Bootstrap.AST.Translator { Failure(Invalid("Unsupported member declaration type")) } - function method TranslateValueTypeDecl(vt: C.ValuetypeDecl): (e: TranslationResult>) + function method TranslateTypeSynonymDecl(ts: C.TypeSynonymDecl): (e: TranslationResult) reads * { - Failure(Invalid("NYI")) + var ty :- TranslateType(ts.Rhs); + Success(E.Type.TypeAlias(E.TypeAlias.TypeAlias(ty))) } - function method TranslateTypeSynonymDecl(ts: C.TypeSynonymDecl): (e: TranslationResult>) + function method TranslateOpaqueTypeDecl(ot: C.OpaqueTypeDecl): (e: TranslationResult) reads * { - Failure(Invalid("NYI")) + Success(E.Type.AbstractType(E.AbstractType.AbstractType())) } - function method TranslateOpaqueTypeDecl(ot: C.OpaqueTypeDecl): (e: TranslationResult>) + function method TranslateNewtypeDecl(nt: C.NewtypeDecl): (e: TranslationResult) reads * { - Failure(Invalid("NYI")) + Success(E.Type.NewType(E.NewType.NewType())) } - function method TranslateNewtypeDecl(nt: C.NewtypeDecl): (e: TranslationResult>) + function method TranslateDatatypeDecl(dt: C.DatatypeDecl): (e: TranslationResult) reads * { - Failure(Invalid("NYI")) + Success(E.Type.DataType(E.DataType.DataType())) } - function method TranslateDatatypeDecl(dt: C.DatatypeDecl): (e: TranslationResult>) + function method TranslateTraitDecl(t: C.TraitDecl): (e: TranslationResult) reads * { - Failure(Invalid("NYI")) + var parentTraits :- Seq.MapResult(ListUtils.ToSeq(t.ParentTraits), (t: C.Type) reads * => + TranslateName(t.AsTraitType.FullDafnyName)); + Success(E.Type.TraitType(E.TraitType.TraitType(parentTraits))) } - function method TranslateTraitDecl(t: C.TraitDecl): (e: TranslationResult>) + function method TranslateClassDecl(c: C.ClassDecl): (e: TranslationResult) reads * { - Failure(Invalid("NYI")) + var parentTraits :- Seq.MapResult(ListUtils.ToSeq(c.ParentTraits), (t: C.Type) reads * => + TranslateName(t.AsTraitType.FullDafnyName)); + Success(E.Type.ClassType(E.ClassType.ClassType(parentTraits))) } - function method TranslateClassDecl(c: C.ClassDecl): (e: TranslationResult>) + function method TranslateEntityInfo(tl: C.TopLevelDecl): (e: TranslationResult) reads * { - Failure(Invalid("NYI")) + var name :- TranslateName(tl.FullDafnyName); + var attrs :- TranslateAttributes(tl.Attributes); + Success(E.EntityInfo(name, attrs := attrs, members := [])) + } + + function method TranslateEntityInfoMembers(tl: C.TopLevelDeclWithMembers): (e: TranslationResult<(seq, E.EntityInfo)>) + reads * + { + var name :- TranslateName(tl.FullDafnyName); + var attrs :- TranslateAttributes(tl.Attributes); + var memberDecls := ListUtils.ToSeq(tl.Members); + var members :- Seq.MapResult(memberDecls, d reads * => TranslateMemberDecl(d)); + var memberNames := Seq.Map((m: E.Entity) => m.ei.name, members); + :- Need(forall nm <- memberNames :: nm.ChildOf(name), Invalid("Malformed name")); + Success((members, E.EntityInfo(name, attrs := attrs, members := memberNames))) } function method TranslateTopLevelDeclWithMembers(tl: C.TopLevelDeclWithMembers): (e: TranslationResult>) reads * { - if tl is C.OpaqueTypeDecl then - TranslateOpaqueTypeDecl(tl) - else if tl is C.NewtypeDecl then - TranslateNewtypeDecl(tl) - else if tl is C.DatatypeDecl then - TranslateDatatypeDecl(tl) - else if tl is C.TraitDecl then - TranslateTraitDecl(tl) - else if tl is C.ClassDecl then - TranslateClassDecl(tl) - else - Failure(Invalid("Unsupported top level declaration type")) + var (members, ei) :- TranslateEntityInfoMembers(tl); + var top :- if tl is C.OpaqueTypeDecl then + TranslateOpaqueTypeDecl(tl) + else if tl is C.NewtypeDecl then + TranslateNewtypeDecl(tl) + else if tl is C.DatatypeDecl then + TranslateDatatypeDecl(tl) + else if tl is C.TraitDecl then + TranslateTraitDecl(tl) + else if tl is C.ClassDecl then + TranslateClassDecl(tl) + else + Failure(Invalid("Unsupported top level declaration type")); + var topEntity := E.Entity.Type(ei, top); + Success([topEntity] + members) } function method TranslateTopLevelDecl(tl: C.TopLevelDecl): (exps: TranslationResult>) @@ -682,8 +723,6 @@ module Bootstrap.AST.Translator { { if tl is C.TopLevelDeclWithMembers then TranslateTopLevelDeclWithMembers(tl) - else if tl is C.ValuetypeDecl then - TranslateValueTypeDecl(tl) else if tl is C.ModuleDecl then var md := tl as C.ModuleDecl; assume ASTHeight(md.Signature) < ASTHeight(tl); @@ -696,8 +735,8 @@ module Bootstrap.AST.Translator { reads * decreases ASTHeight(sig), 1 { - var name := TranslateName(sig.ModuleDef.FullName); - var attrs := sig.ModuleDef.Attributes; + var name :- TranslateName(sig.ModuleDef.FullDafnyName); + var attrs :- TranslateAttributes(sig.ModuleDef.Attributes); var includes := ListUtils.ToSeq(sig.ModuleDef.Includes); var exports := sig.ExportSets; var topLevels := DictUtils.DictionaryToSeq(sig.TopLevels); @@ -706,7 +745,9 @@ module Bootstrap.AST.Translator { assume ASTHeight(tl.1) < ASTHeight(sig); TranslateTopLevelDecl(tl.1)); var topDecls' := Seq.Flatten(topDecls); - var ei := E.EntityInfo.Mk(name); // TODO + var topNames := Seq.Map((d: E.Entity) => d.ei.name, topDecls'); + :- Need(forall nm <- topNames :: nm.ChildOf(name), Invalid("Malformed name")); + var ei := E.EntityInfo(name, attrs := attrs, members := topNames); var mod := E.Entity.Module(ei, E.Module.Module()); Success([mod] + topDecls') } @@ -718,8 +759,8 @@ module Bootstrap.AST.Translator { var entities :- Seq.MapResult(moduleSigs, (sig: (C.ModuleDefinition, C.ModuleSignature)) reads * => TranslateModule(sig.1)); var regMap := Seq.FoldL((m:map, e: E.Entity) => m + map[e.ei.name := e], map[], Seq.Flatten(entities)); - var mainMethodName := TranslateName(p.MainMethod.FullName); - var defaultModuleName := TranslateName(p.DefaultModule.FullName); + var mainMethodName :- TranslateName(p.MainMethod.FullDafnyName); + var defaultModuleName :- TranslateName(p.DefaultModule.FullDafnyName); var reg := E.Registry_.Registry(regMap); :- Need(reg.Validate().Pass?, Invalid("Failed to validate registry")); var prog := E.Program(reg, defaultModule := defaultModuleName, mainMethod := Some(mainMethodName)); From 694a4f3b654ba302e250fbfdb2a0b88d0884b032 Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Thu, 25 Aug 2022 17:42:39 -0700 Subject: [PATCH 045/105] ast: Move entity translations into another module --- src/AST/Translator.Entity.dfy | 225 +++++++++++++++++++++++++++++ src/AST/Translator.Expressions.dfy | 213 +-------------------------- 2 files changed, 226 insertions(+), 212 deletions(-) create mode 100644 src/AST/Translator.Entity.dfy diff --git a/src/AST/Translator.Entity.dfy b/src/AST/Translator.Entity.dfy new file mode 100644 index 00000000..342aa4b5 --- /dev/null +++ b/src/AST/Translator.Entity.dfy @@ -0,0 +1,225 @@ +include "Translator.Expressions.dfy" + +module Bootstrap.AST.Translator.Entity { + import opened Utils.Lib + import opened Utils.Lib.Datatypes + import opened Interop.CSharpInterop + import opened Interop.CSharpDafnyInterop + import opened Interop.CSharpDafnyASTInterop + import C = Interop.CSharpDafnyASTModel + import E = Entities + import N = Names + import Expr = Expressions + import opened Common + + function method TranslateName(str: System.String): TranslationResult { + var name := TypeConv.AsString(str); + var parts := Seq.Split('.', name); + :- Need(forall s | s in parts :: s != "", Invalid("Empty component in name")); + assert forall s | s in parts :: '.' !in s; + assert forall s | s in parts :: s != "" && '.' !in s; + var atoms : seq := parts; + Success(Seq.FoldL((n: N.Name, a: N.Atom) => N.Name(n, a), N.Anonymous, atoms)) + } + + function method TranslateAttributeName(s: string): E.AttributeName { + match s { + case "axiom" => E.AttributeName.Axiom + case "extern" => E.AttributeName.Extern + case _ => E.AttributeName.UserAttribute(s) + } + } + + function method TranslateAttributes(attrs: C.Attributes): TranslationResult> + reads * + { + var name := TypeConv.AsString(attrs.Name); + var args :- Seq.MapResult(ListUtils.ToSeq(attrs.Args), Expr.TranslateExpression); + // TODO: Attributes needs to be nullable + //var rest :- if attrs.Prev == null then Success([]) else TranslateAttributes(attrs.Prev); + var rest := []; + Success([E.Attribute.Attribute(TranslateAttributeName(name), args)] + rest) + } + + function method TranslateField(f: C.Field): (d: TranslationResult) + reads * + { + var name :- TranslateName(f.FullDafnyName); + var attrs :- TranslateAttributes(f.Attributes); + var kind := if f.IsMutable then E.Var else E.Const; + var ei := E.EntityInfo(name, attrs := attrs, members := []); + if f is C.ConstantField then + var init :- Expr.TranslateExpression((f as C.ConstantField).Rhs); + Success(E.Definition(ei, E.Definition.Field(E.Field.Field(kind, Some(init))))) + else + Success(E.Definition(ei, E.Definition.Field(E.Field.Field(kind, None)))) + } + + function method TranslateMethod(m: C.Method): (d: TranslationResult) + reads * + { + var name :- TranslateName(m.FullDafnyName); + var attrs :- TranslateAttributes(m.Attributes); + var body :- Expr.TranslateStatement(m.Body); + var def := if m is C.Constructor then + E.Constructor(body) + else + E.Method(body); + var ei := E.EntityInfo(name, attrs := attrs, members := []); + Success(E.Definition(ei, E.Callable(def))) + } + + function method TranslateFunction(f: C.Function): (d: TranslationResult) + reads * + { + var name :- TranslateName(f.FullDafnyName); + var attrs :- TranslateAttributes(f.Attributes); + var body :- Expr.TranslateExpression(f.Body); + var ei := E.EntityInfo(name, attrs := attrs, members := []); + Success(E.Definition(ei, E.Callable(E.Function(body)))) + } + + function method TranslateMemberDecl(md: C.MemberDecl): (d: TranslationResult) + reads * + { + if md is C.Field then + TranslateField(md) + else if md is C.Function then + TranslateFunction(md) + else if md is C.Method then + TranslateMethod(md) + else + Failure(Invalid("Unsupported member declaration type")) + } + + function method TranslateTypeSynonymDecl(ts: C.TypeSynonymDecl): (e: TranslationResult) + reads * + { + var ty :- Expr.TranslateType(ts.Rhs); + Success(E.Type.TypeAlias(E.TypeAlias.TypeAlias(ty))) + } + + function method TranslateOpaqueTypeDecl(ot: C.OpaqueTypeDecl): (e: TranslationResult) + reads * + { + Success(E.Type.AbstractType(E.AbstractType.AbstractType())) + } + + function method TranslateNewtypeDecl(nt: C.NewtypeDecl): (e: TranslationResult) + reads * + { + Success(E.Type.NewType(E.NewType.NewType())) + } + + function method TranslateDatatypeDecl(dt: C.DatatypeDecl): (e: TranslationResult) + reads * + { + Success(E.Type.DataType(E.DataType.DataType())) + } + + function method TranslateTraitDecl(t: C.TraitDecl): (e: TranslationResult) + reads * + { + var parentTraits :- Seq.MapResult(ListUtils.ToSeq(t.ParentTraits), (t: C.Type) reads * => + TranslateName(t.AsTraitType.FullDafnyName)); + Success(E.Type.TraitType(E.TraitType.TraitType(parentTraits))) + } + + function method TranslateClassDecl(c: C.ClassDecl): (e: TranslationResult) + reads * + { + var parentTraits :- Seq.MapResult(ListUtils.ToSeq(c.ParentTraits), (t: C.Type) reads * => + TranslateName(t.AsTraitType.FullDafnyName)); + Success(E.Type.ClassType(E.ClassType.ClassType(parentTraits))) + } + + function method TranslateEntityInfo(tl: C.TopLevelDecl): (e: TranslationResult) + reads * + { + var name :- TranslateName(tl.FullDafnyName); + var attrs :- TranslateAttributes(tl.Attributes); + Success(E.EntityInfo(name, attrs := attrs, members := [])) + } + + function method TranslateEntityInfoMembers(tl: C.TopLevelDeclWithMembers): (e: TranslationResult<(seq, E.EntityInfo)>) + reads * + { + var name :- TranslateName(tl.FullDafnyName); + var attrs :- TranslateAttributes(tl.Attributes); + var memberDecls := ListUtils.ToSeq(tl.Members); + var members :- Seq.MapResult(memberDecls, d reads * => TranslateMemberDecl(d)); + var memberNames := Seq.Map((m: E.Entity) => m.ei.name, members); + :- Need(forall nm <- memberNames :: nm.ChildOf(name), Invalid("Malformed name")); + Success((members, E.EntityInfo(name, attrs := attrs, members := memberNames))) + } + + function method TranslateTopLevelDeclWithMembers(tl: C.TopLevelDeclWithMembers): (e: TranslationResult>) + reads * + { + var (members, ei) :- TranslateEntityInfoMembers(tl); + var top :- if tl is C.OpaqueTypeDecl then + TranslateOpaqueTypeDecl(tl) + else if tl is C.NewtypeDecl then + TranslateNewtypeDecl(tl) + else if tl is C.DatatypeDecl then + TranslateDatatypeDecl(tl) + else if tl is C.TraitDecl then + TranslateTraitDecl(tl) + else if tl is C.ClassDecl then + TranslateClassDecl(tl) + else + Failure(Invalid("Unsupported top level declaration type")); + var topEntity := E.Entity.Type(ei, top); + Success([topEntity] + members) + } + + function method TranslateTopLevelDecl(tl: C.TopLevelDecl): (exps: TranslationResult>) + reads * + decreases ASTHeight(tl), 0 + { + if tl is C.TopLevelDeclWithMembers then + TranslateTopLevelDeclWithMembers(tl) + else if tl is C.ModuleDecl then + var md := tl as C.ModuleDecl; + assume ASTHeight(md.Signature) < ASTHeight(tl); + TranslateModule(md.Signature) + else + Failure(Invalid("Unsupported top level declaration type")) + } + + function method TranslateModule(sig: C.ModuleSignature): (m: TranslationResult>) + reads * + decreases ASTHeight(sig), 1 + { + var name :- TranslateName(sig.ModuleDef.FullDafnyName); + var attrs :- TranslateAttributes(sig.ModuleDef.Attributes); + var includes := ListUtils.ToSeq(sig.ModuleDef.Includes); + var exports := sig.ExportSets; + var topLevels := DictUtils.DictionaryToSeq(sig.TopLevels); + var topDecls :- Seq.MapResult(topLevels, + (tl: (System.String, C.TopLevelDecl)) reads * => + assume ASTHeight(tl.1) < ASTHeight(sig); + TranslateTopLevelDecl(tl.1)); + var topDecls' := Seq.Flatten(topDecls); + var topNames := Seq.Map((d: E.Entity) => d.ei.name, topDecls'); + :- Need(forall nm <- topNames :: nm.ChildOf(name), Invalid("Malformed name")); + var ei := E.EntityInfo(name, attrs := attrs, members := topNames); + var mod := E.Entity.Module(ei, E.Module.Module()); + Success([mod] + topDecls') + } + + function method TranslateProgram(p: C.Program): (exps: TranslationResult) + reads * + { + var moduleSigs := DictUtils.DictionaryToSeq(p.ModuleSigs); + var entities :- Seq.MapResult(moduleSigs, + (sig: (C.ModuleDefinition, C.ModuleSignature)) reads * => TranslateModule(sig.1)); + var regMap := Seq.FoldL((m:map, e: E.Entity) => m + map[e.ei.name := e], map[], Seq.Flatten(entities)); + var mainMethodName :- TranslateName(p.MainMethod.FullDafnyName); + var defaultModuleName :- TranslateName(p.DefaultModule.FullDafnyName); + var reg := E.Registry_.Registry(regMap); + :- Need(reg.Validate().Pass?, Invalid("Failed to validate registry")); + var prog := E.Program(reg, defaultModule := defaultModuleName, mainMethod := Some(mainMethodName)); + if prog.Valid?() then Success(prog) else Failure(Invalid("Generated invalid program")) + } +} \ No newline at end of file diff --git a/src/AST/Translator.Expressions.dfy b/src/AST/Translator.Expressions.dfy index 17b32be0..417c49b0 100644 --- a/src/AST/Translator.Expressions.dfy +++ b/src/AST/Translator.Expressions.dfy @@ -8,7 +8,7 @@ include "Syntax.dfy" include "Predicates.dfy" include "Translator.Common.dfy" -module Bootstrap.AST.Translator { +module Bootstrap.AST.Translator.Expressions { import opened Utils.Lib import opened Utils.Lib.Datatypes import opened Interop.CSharpInterop @@ -555,215 +555,4 @@ module Bootstrap.AST.Translator { Success(D.Program(tm)) } */ - - function method TranslateName(str: System.String): TranslationResult { - var name := TypeConv.AsString(str); - var parts := Seq.Split('.', name); - :- Need(forall s | s in parts :: s != "", Invalid("Empty component in name")); - assert forall s | s in parts :: '.' !in s; - assert forall s | s in parts :: s != "" && '.' !in s; - var atoms : seq := parts; - Success(Seq.FoldL((n: N.Name, a: N.Atom) => N.Name(n, a), N.Anonymous, atoms)) - } - - function method TranslateAttributeName(s: string): E.AttributeName { - match s { - case "axiom" => E.AttributeName.Axiom - case "extern" => E.AttributeName.Extern - case _ => E.AttributeName.UserAttribute(s) - } - } - - function method TranslateAttributes(attrs: C.Attributes): TranslationResult> - reads * - { - var name := TypeConv.AsString(attrs.Name); - var args :- Seq.MapResult(ListUtils.ToSeq(attrs.Args), TranslateExpression); - // TODO: Attributes needs to be nullable - //var rest :- if attrs.Prev == null then Success([]) else TranslateAttributes(attrs.Prev); - var rest := []; - Success([E.Attribute.Attribute(TranslateAttributeName(name), args)] + rest) - } - - function method TranslateField(f: C.Field): (d: TranslationResult) - reads * - { - var name :- TranslateName(f.FullDafnyName); - var attrs :- TranslateAttributes(f.Attributes); - var kind := if f.IsMutable then E.Var else E.Const; - var ei := E.EntityInfo(name, attrs := attrs, members := []); - if f is C.ConstantField then - var init :- TranslateExpression((f as C.ConstantField).Rhs); - Success(E.Definition(ei, E.Definition.Field(E.Field.Field(kind, Some(init))))) - else - Success(E.Definition(ei, E.Definition.Field(E.Field.Field(kind, None)))) - } - - function method TranslateMethod(m: C.Method): (d: TranslationResult) - reads * - { - var name :- TranslateName(m.FullDafnyName); - var attrs :- TranslateAttributes(m.Attributes); - var body :- TranslateStatement(m.Body); - var def := if m is C.Constructor then - E.Constructor(body) - else - E.Method(body); - var ei := E.EntityInfo(name, attrs := attrs, members := []); - Success(E.Definition(ei, E.Callable(def))) - } - - function method TranslateFunction(f: C.Function): (d: TranslationResult) - reads * - { - var name :- TranslateName(f.FullDafnyName); - var attrs :- TranslateAttributes(f.Attributes); - var body :- TranslateExpression(f.Body); - var ei := E.EntityInfo(name, attrs := attrs, members := []); - Success(E.Definition(ei, E.Callable(E.Function(body)))) - } - - function method TranslateMemberDecl(md: C.MemberDecl): (d: TranslationResult) - reads * - { - if md is C.Field then - TranslateField(md) - else if md is C.Function then - TranslateFunction(md) - else if md is C.Method then - TranslateMethod(md) - else - Failure(Invalid("Unsupported member declaration type")) - } - - function method TranslateTypeSynonymDecl(ts: C.TypeSynonymDecl): (e: TranslationResult) - reads * - { - var ty :- TranslateType(ts.Rhs); - Success(E.Type.TypeAlias(E.TypeAlias.TypeAlias(ty))) - } - - function method TranslateOpaqueTypeDecl(ot: C.OpaqueTypeDecl): (e: TranslationResult) - reads * - { - Success(E.Type.AbstractType(E.AbstractType.AbstractType())) - } - - function method TranslateNewtypeDecl(nt: C.NewtypeDecl): (e: TranslationResult) - reads * - { - Success(E.Type.NewType(E.NewType.NewType())) - } - - function method TranslateDatatypeDecl(dt: C.DatatypeDecl): (e: TranslationResult) - reads * - { - Success(E.Type.DataType(E.DataType.DataType())) - } - - function method TranslateTraitDecl(t: C.TraitDecl): (e: TranslationResult) - reads * - { - var parentTraits :- Seq.MapResult(ListUtils.ToSeq(t.ParentTraits), (t: C.Type) reads * => - TranslateName(t.AsTraitType.FullDafnyName)); - Success(E.Type.TraitType(E.TraitType.TraitType(parentTraits))) - } - - function method TranslateClassDecl(c: C.ClassDecl): (e: TranslationResult) - reads * - { - var parentTraits :- Seq.MapResult(ListUtils.ToSeq(c.ParentTraits), (t: C.Type) reads * => - TranslateName(t.AsTraitType.FullDafnyName)); - Success(E.Type.ClassType(E.ClassType.ClassType(parentTraits))) - } - - function method TranslateEntityInfo(tl: C.TopLevelDecl): (e: TranslationResult) - reads * - { - var name :- TranslateName(tl.FullDafnyName); - var attrs :- TranslateAttributes(tl.Attributes); - Success(E.EntityInfo(name, attrs := attrs, members := [])) - } - - function method TranslateEntityInfoMembers(tl: C.TopLevelDeclWithMembers): (e: TranslationResult<(seq, E.EntityInfo)>) - reads * - { - var name :- TranslateName(tl.FullDafnyName); - var attrs :- TranslateAttributes(tl.Attributes); - var memberDecls := ListUtils.ToSeq(tl.Members); - var members :- Seq.MapResult(memberDecls, d reads * => TranslateMemberDecl(d)); - var memberNames := Seq.Map((m: E.Entity) => m.ei.name, members); - :- Need(forall nm <- memberNames :: nm.ChildOf(name), Invalid("Malformed name")); - Success((members, E.EntityInfo(name, attrs := attrs, members := memberNames))) - } - - function method TranslateTopLevelDeclWithMembers(tl: C.TopLevelDeclWithMembers): (e: TranslationResult>) - reads * - { - var (members, ei) :- TranslateEntityInfoMembers(tl); - var top :- if tl is C.OpaqueTypeDecl then - TranslateOpaqueTypeDecl(tl) - else if tl is C.NewtypeDecl then - TranslateNewtypeDecl(tl) - else if tl is C.DatatypeDecl then - TranslateDatatypeDecl(tl) - else if tl is C.TraitDecl then - TranslateTraitDecl(tl) - else if tl is C.ClassDecl then - TranslateClassDecl(tl) - else - Failure(Invalid("Unsupported top level declaration type")); - var topEntity := E.Entity.Type(ei, top); - Success([topEntity] + members) - } - - function method TranslateTopLevelDecl(tl: C.TopLevelDecl): (exps: TranslationResult>) - reads * - decreases ASTHeight(tl), 0 - { - if tl is C.TopLevelDeclWithMembers then - TranslateTopLevelDeclWithMembers(tl) - else if tl is C.ModuleDecl then - var md := tl as C.ModuleDecl; - assume ASTHeight(md.Signature) < ASTHeight(tl); - TranslateModule(md.Signature) - else - Failure(Invalid("Unsupported top level declaration type")) - } - - function method TranslateModule(sig: C.ModuleSignature): (m: TranslationResult>) - reads * - decreases ASTHeight(sig), 1 - { - var name :- TranslateName(sig.ModuleDef.FullDafnyName); - var attrs :- TranslateAttributes(sig.ModuleDef.Attributes); - var includes := ListUtils.ToSeq(sig.ModuleDef.Includes); - var exports := sig.ExportSets; - var topLevels := DictUtils.DictionaryToSeq(sig.TopLevels); - var topDecls :- Seq.MapResult(topLevels, - (tl: (System.String, C.TopLevelDecl)) reads * => - assume ASTHeight(tl.1) < ASTHeight(sig); - TranslateTopLevelDecl(tl.1)); - var topDecls' := Seq.Flatten(topDecls); - var topNames := Seq.Map((d: E.Entity) => d.ei.name, topDecls'); - :- Need(forall nm <- topNames :: nm.ChildOf(name), Invalid("Malformed name")); - var ei := E.EntityInfo(name, attrs := attrs, members := topNames); - var mod := E.Entity.Module(ei, E.Module.Module()); - Success([mod] + topDecls') - } - - function method TranslateProgram(p: C.Program): (exps: TranslationResult) - reads * - { - var moduleSigs := DictUtils.DictionaryToSeq(p.ModuleSigs); - var entities :- Seq.MapResult(moduleSigs, - (sig: (C.ModuleDefinition, C.ModuleSignature)) reads * => TranslateModule(sig.1)); - var regMap := Seq.FoldL((m:map, e: E.Entity) => m + map[e.ei.name := e], map[], Seq.Flatten(entities)); - var mainMethodName :- TranslateName(p.MainMethod.FullDafnyName); - var defaultModuleName :- TranslateName(p.DefaultModule.FullDafnyName); - var reg := E.Registry_.Registry(regMap); - :- Need(reg.Validate().Pass?, Invalid("Failed to validate registry")); - var prog := E.Program(reg, defaultModule := defaultModuleName, mainMethod := Some(mainMethodName)); - if prog.Valid?() then Success(prog) else Failure(Invalid("Generated invalid program")) - } } From 30723276547f5b54bdf7992d9a49b5c06150a9cc Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Mon, 29 Aug 2022 11:19:42 -0700 Subject: [PATCH 046/105] ast: Move `Translator.Entity` to `-functionSyntax:4` --- src/AST/Translator.Entity.dfy | 42 +++++++++++++++++------------------ 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/AST/Translator.Entity.dfy b/src/AST/Translator.Entity.dfy index 342aa4b5..6e8fbf4b 100644 --- a/src/AST/Translator.Entity.dfy +++ b/src/AST/Translator.Entity.dfy @@ -1,6 +1,6 @@ include "Translator.Expressions.dfy" -module Bootstrap.AST.Translator.Entity { +module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { import opened Utils.Lib import opened Utils.Lib.Datatypes import opened Interop.CSharpInterop @@ -12,7 +12,7 @@ module Bootstrap.AST.Translator.Entity { import Expr = Expressions import opened Common - function method TranslateName(str: System.String): TranslationResult { + function TranslateName(str: System.String): TranslationResult { var name := TypeConv.AsString(str); var parts := Seq.Split('.', name); :- Need(forall s | s in parts :: s != "", Invalid("Empty component in name")); @@ -22,7 +22,7 @@ module Bootstrap.AST.Translator.Entity { Success(Seq.FoldL((n: N.Name, a: N.Atom) => N.Name(n, a), N.Anonymous, atoms)) } - function method TranslateAttributeName(s: string): E.AttributeName { + function TranslateAttributeName(s: string): E.AttributeName { match s { case "axiom" => E.AttributeName.Axiom case "extern" => E.AttributeName.Extern @@ -30,7 +30,7 @@ module Bootstrap.AST.Translator.Entity { } } - function method TranslateAttributes(attrs: C.Attributes): TranslationResult> + function TranslateAttributes(attrs: C.Attributes): TranslationResult> reads * { var name := TypeConv.AsString(attrs.Name); @@ -41,7 +41,7 @@ module Bootstrap.AST.Translator.Entity { Success([E.Attribute.Attribute(TranslateAttributeName(name), args)] + rest) } - function method TranslateField(f: C.Field): (d: TranslationResult) + function TranslateField(f: C.Field): (d: TranslationResult) reads * { var name :- TranslateName(f.FullDafnyName); @@ -55,7 +55,7 @@ module Bootstrap.AST.Translator.Entity { Success(E.Definition(ei, E.Definition.Field(E.Field.Field(kind, None)))) } - function method TranslateMethod(m: C.Method): (d: TranslationResult) + function TranslateMethod(m: C.Method): (d: TranslationResult) reads * { var name :- TranslateName(m.FullDafnyName); @@ -69,7 +69,7 @@ module Bootstrap.AST.Translator.Entity { Success(E.Definition(ei, E.Callable(def))) } - function method TranslateFunction(f: C.Function): (d: TranslationResult) + function TranslateFunction(f: C.Function): (d: TranslationResult) reads * { var name :- TranslateName(f.FullDafnyName); @@ -79,7 +79,7 @@ module Bootstrap.AST.Translator.Entity { Success(E.Definition(ei, E.Callable(E.Function(body)))) } - function method TranslateMemberDecl(md: C.MemberDecl): (d: TranslationResult) + function TranslateMemberDecl(md: C.MemberDecl): (d: TranslationResult) reads * { if md is C.Field then @@ -92,32 +92,32 @@ module Bootstrap.AST.Translator.Entity { Failure(Invalid("Unsupported member declaration type")) } - function method TranslateTypeSynonymDecl(ts: C.TypeSynonymDecl): (e: TranslationResult) + function TranslateTypeSynonymDecl(ts: C.TypeSynonymDecl): (e: TranslationResult) reads * { var ty :- Expr.TranslateType(ts.Rhs); Success(E.Type.TypeAlias(E.TypeAlias.TypeAlias(ty))) } - function method TranslateOpaqueTypeDecl(ot: C.OpaqueTypeDecl): (e: TranslationResult) + function TranslateOpaqueTypeDecl(ot: C.OpaqueTypeDecl): (e: TranslationResult) reads * { Success(E.Type.AbstractType(E.AbstractType.AbstractType())) } - function method TranslateNewtypeDecl(nt: C.NewtypeDecl): (e: TranslationResult) + function TranslateNewtypeDecl(nt: C.NewtypeDecl): (e: TranslationResult) reads * { Success(E.Type.NewType(E.NewType.NewType())) } - function method TranslateDatatypeDecl(dt: C.DatatypeDecl): (e: TranslationResult) + function TranslateDatatypeDecl(dt: C.DatatypeDecl): (e: TranslationResult) reads * { Success(E.Type.DataType(E.DataType.DataType())) } - function method TranslateTraitDecl(t: C.TraitDecl): (e: TranslationResult) + function TranslateTraitDecl(t: C.TraitDecl): (e: TranslationResult) reads * { var parentTraits :- Seq.MapResult(ListUtils.ToSeq(t.ParentTraits), (t: C.Type) reads * => @@ -125,7 +125,7 @@ module Bootstrap.AST.Translator.Entity { Success(E.Type.TraitType(E.TraitType.TraitType(parentTraits))) } - function method TranslateClassDecl(c: C.ClassDecl): (e: TranslationResult) + function TranslateClassDecl(c: C.ClassDecl): (e: TranslationResult) reads * { var parentTraits :- Seq.MapResult(ListUtils.ToSeq(c.ParentTraits), (t: C.Type) reads * => @@ -133,7 +133,7 @@ module Bootstrap.AST.Translator.Entity { Success(E.Type.ClassType(E.ClassType.ClassType(parentTraits))) } - function method TranslateEntityInfo(tl: C.TopLevelDecl): (e: TranslationResult) + function TranslateTopLevelEntityInfo(tl: C.TopLevelDecl): (e: TranslationResult) reads * { var name :- TranslateName(tl.FullDafnyName); @@ -141,7 +141,7 @@ module Bootstrap.AST.Translator.Entity { Success(E.EntityInfo(name, attrs := attrs, members := [])) } - function method TranslateEntityInfoMembers(tl: C.TopLevelDeclWithMembers): (e: TranslationResult<(seq, E.EntityInfo)>) + function TranslateTopLevelEntityInfoMembers(tl: C.TopLevelDeclWithMembers): (e: TranslationResult<(seq, E.EntityInfo)>) reads * { var name :- TranslateName(tl.FullDafnyName); @@ -153,10 +153,10 @@ module Bootstrap.AST.Translator.Entity { Success((members, E.EntityInfo(name, attrs := attrs, members := memberNames))) } - function method TranslateTopLevelDeclWithMembers(tl: C.TopLevelDeclWithMembers): (e: TranslationResult>) + function TranslateTopLevelDeclWithMembers(tl: C.TopLevelDeclWithMembers): (e: TranslationResult>) reads * { - var (members, ei) :- TranslateEntityInfoMembers(tl); + var (members, ei) :- TranslateTopLevelEntityInfoMembers(tl); var top :- if tl is C.OpaqueTypeDecl then TranslateOpaqueTypeDecl(tl) else if tl is C.NewtypeDecl then @@ -173,7 +173,7 @@ module Bootstrap.AST.Translator.Entity { Success([topEntity] + members) } - function method TranslateTopLevelDecl(tl: C.TopLevelDecl): (exps: TranslationResult>) + function TranslateTopLevelDecl(tl: C.TopLevelDecl): (exps: TranslationResult>) reads * decreases ASTHeight(tl), 0 { @@ -187,7 +187,7 @@ module Bootstrap.AST.Translator.Entity { Failure(Invalid("Unsupported top level declaration type")) } - function method TranslateModule(sig: C.ModuleSignature): (m: TranslationResult>) + function TranslateModule(sig: C.ModuleSignature): (m: TranslationResult>) reads * decreases ASTHeight(sig), 1 { @@ -208,7 +208,7 @@ module Bootstrap.AST.Translator.Entity { Success([mod] + topDecls') } - function method TranslateProgram(p: C.Program): (exps: TranslationResult) + function TranslateProgram(p: C.Program): (exps: TranslationResult) reads * { var moduleSigs := DictUtils.DictionaryToSeq(p.ModuleSigs); From 477eed1db2216c67438e3e61731790ffbf99dde3 Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Mon, 29 Aug 2022 11:23:39 -0700 Subject: [PATCH 047/105] ast: Add location to `EntityInfo` --- src/AST/Entities.dfy | 14 +++++++--- src/AST/Translator.Entity.dfy | 45 ++++++++++++++++++++++---------- src/Interop/CSharpDafnyModel.dfy | 8 +++++- 3 files changed, 49 insertions(+), 18 deletions(-) diff --git a/src/AST/Entities.dfy b/src/AST/Entities.dfy index 615ea479..22deeadf 100644 --- a/src/AST/Entities.dfy +++ b/src/AST/Entities.dfy @@ -74,15 +74,23 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities | EType | EDefinition + datatype Location = + Location(file: string, line: int, column: int) + { + static function EMPTY(): Location { + Location("", 0, 0) + } + } + type EntityInfo = e: EntityInfo_ | e.Valid?() witness EntityInfo_.EMPTY() datatype EntityInfo_ = - EntityInfo(name: Name, nameonly attrs: seq, nameonly members: seq) + EntityInfo(name: Name, nameonly location: Location, nameonly attrs: seq, nameonly members: seq) { static function EMPTY(): (ei: EntityInfo_) ensures ei.Valid?() { - EntityInfo(Anonymous, attrs := [], members := []) + EntityInfo(Anonymous, location := Location.EMPTY(), attrs := [], members := []) } ghost predicate Valid?() { @@ -92,7 +100,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities static function Mk(name: Name): EntityInfo // Construct an `EntityInfo` instance with no attributes and no members. { - EntityInfo(name, attrs := [], members := []) + EntityInfo(name, location := Location.EMPTY(), attrs := [], members := []) } } diff --git a/src/AST/Translator.Entity.dfy b/src/AST/Translator.Entity.dfy index 6e8fbf4b..77b6ff63 100644 --- a/src/AST/Translator.Entity.dfy +++ b/src/AST/Translator.Entity.dfy @@ -22,6 +22,15 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { Success(Seq.FoldL((n: N.Name, a: N.Atom) => N.Name(n, a), N.Anonymous, atoms)) } + function TranslateLocation(tok: Microsoft.Boogie.IToken): E.Location + reads * + { + var filename := TypeConv.AsString(tok.FileName); + var line := tok.Line; + var col := tok.Column; + E.Location(filename, line as int, col as int) + } + function TranslateAttributeName(s: string): E.AttributeName { match s { case "axiom" => E.AttributeName.Axiom @@ -41,13 +50,20 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { Success([E.Attribute.Attribute(TranslateAttributeName(name), args)] + rest) } + function TranslateMemberEntityInfo(md: C.MemberDecl): (e: TranslationResult) + reads * + { + var name :- TranslateName(md.FullDafnyName); + var attrs :- TranslateAttributes(md.Attributes); + var loc := TranslateLocation(md.tok); + Success(E.EntityInfo(name, location := loc, attrs := attrs, members := [])) + } + function TranslateField(f: C.Field): (d: TranslationResult) reads * { - var name :- TranslateName(f.FullDafnyName); - var attrs :- TranslateAttributes(f.Attributes); var kind := if f.IsMutable then E.Var else E.Const; - var ei := E.EntityInfo(name, attrs := attrs, members := []); + var ei :- TranslateMemberEntityInfo(f); if f is C.ConstantField then var init :- Expr.TranslateExpression((f as C.ConstantField).Rhs); Success(E.Definition(ei, E.Definition.Field(E.Field.Field(kind, Some(init))))) @@ -58,25 +74,22 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { function TranslateMethod(m: C.Method): (d: TranslationResult) reads * { - var name :- TranslateName(m.FullDafnyName); - var attrs :- TranslateAttributes(m.Attributes); var body :- Expr.TranslateStatement(m.Body); var def := if m is C.Constructor then E.Constructor(body) else E.Method(body); var ei := E.EntityInfo(name, attrs := attrs, members := []); + var ei :- TranslateMemberEntityInfo(m); Success(E.Definition(ei, E.Callable(def))) } function TranslateFunction(f: C.Function): (d: TranslationResult) reads * { - var name :- TranslateName(f.FullDafnyName); - var attrs :- TranslateAttributes(f.Attributes); var body :- Expr.TranslateExpression(f.Body); - var ei := E.EntityInfo(name, attrs := attrs, members := []); Success(E.Definition(ei, E.Callable(E.Function(body)))) + var ei :- TranslateMemberEntityInfo(f); } function TranslateMemberDecl(md: C.MemberDecl): (d: TranslationResult) @@ -138,7 +151,8 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { { var name :- TranslateName(tl.FullDafnyName); var attrs :- TranslateAttributes(tl.Attributes); - Success(E.EntityInfo(name, attrs := attrs, members := [])) + var loc := TranslateLocation(tl.tok); + Success(E.EntityInfo(name, location := loc, attrs := attrs, members := [])) } function TranslateTopLevelEntityInfoMembers(tl: C.TopLevelDeclWithMembers): (e: TranslationResult<(seq, E.EntityInfo)>) @@ -146,11 +160,12 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { { var name :- TranslateName(tl.FullDafnyName); var attrs :- TranslateAttributes(tl.Attributes); + var loc := TranslateLocation(tl.tok); var memberDecls := ListUtils.ToSeq(tl.Members); var members :- Seq.MapResult(memberDecls, d reads * => TranslateMemberDecl(d)); var memberNames := Seq.Map((m: E.Entity) => m.ei.name, members); :- Need(forall nm <- memberNames :: nm.ChildOf(name), Invalid("Malformed name")); - Success((members, E.EntityInfo(name, attrs := attrs, members := memberNames))) + Success((members, E.EntityInfo(name, location := loc, attrs := attrs, members := memberNames))) } function TranslateTopLevelDeclWithMembers(tl: C.TopLevelDeclWithMembers): (e: TranslationResult>) @@ -191,9 +206,11 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { reads * decreases ASTHeight(sig), 1 { - var name :- TranslateName(sig.ModuleDef.FullDafnyName); - var attrs :- TranslateAttributes(sig.ModuleDef.Attributes); - var includes := ListUtils.ToSeq(sig.ModuleDef.Includes); + var def := sig.ModuleDef; + var name :- TranslateName(def.FullDafnyName); + var attrs :- TranslateAttributes(def.Attributes); + var loc := TranslateLocation(def.tok); + var includes := ListUtils.ToSeq(def.Includes); var exports := sig.ExportSets; var topLevels := DictUtils.DictionaryToSeq(sig.TopLevels); var topDecls :- Seq.MapResult(topLevels, @@ -203,7 +220,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { var topDecls' := Seq.Flatten(topDecls); var topNames := Seq.Map((d: E.Entity) => d.ei.name, topDecls'); :- Need(forall nm <- topNames :: nm.ChildOf(name), Invalid("Malformed name")); - var ei := E.EntityInfo(name, attrs := attrs, members := topNames); + var ei := E.EntityInfo(name, location := loc, attrs := attrs, members := topNames); var mod := E.Entity.Module(ei, E.Module.Module()); Success([mod] + topDecls') } diff --git a/src/Interop/CSharpDafnyModel.dfy b/src/Interop/CSharpDafnyModel.dfy index a13f524a..25c2786d 100644 --- a/src/Interop/CSharpDafnyModel.dfy +++ b/src/Interop/CSharpDafnyModel.dfy @@ -30,7 +30,13 @@ module {:extern "Microsoft.Dafny"} {:compile false} Microsoft.Dafny { } module {:extern "Microsoft.Boogie"} {:compile false} Microsoft.Boogie { - trait {:compile false} {:extern} {:termination false} IToken {} + import System + + trait {:compile false} {:extern} {:termination false} IToken { + var {:extern "filename"} FileName: System.String + var {:extern "line"} Line: System.int32 + var {:extern "col"} Column: System.int32 + } class {:extern} {:compile false} ErrorReporter {} class {:extern} {:compile false} Expr {} class {:extern} {:compile false} Function {} From 4466796394f8f26e7b8e87dd01a31f85cd954568 Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Mon, 29 Aug 2022 11:24:26 -0700 Subject: [PATCH 048/105] ast: Improve entity translation error messages --- src/AST/Translator.Entity.dfy | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/AST/Translator.Entity.dfy b/src/AST/Translator.Entity.dfy index 77b6ff63..440ac492 100644 --- a/src/AST/Translator.Entity.dfy +++ b/src/AST/Translator.Entity.dfy @@ -15,7 +15,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { function TranslateName(str: System.String): TranslationResult { var name := TypeConv.AsString(str); var parts := Seq.Split('.', name); - :- Need(forall s | s in parts :: s != "", Invalid("Empty component in name")); + :- Need(forall s | s in parts :: s != "", Invalid("Empty component in name: " + name)); assert forall s | s in parts :: '.' !in s; assert forall s | s in parts :: s != "" && '.' !in s; var atoms : seq := parts; @@ -102,7 +102,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { else if md is C.Method then TranslateMethod(md) else - Failure(Invalid("Unsupported member declaration type")) + Failure(Invalid("Unsupported member declaration type: " + TypeConv.AsString(md.FullDafnyName))) } function TranslateTypeSynonymDecl(ts: C.TypeSynonymDecl): (e: TranslationResult) @@ -164,7 +164,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { var memberDecls := ListUtils.ToSeq(tl.Members); var members :- Seq.MapResult(memberDecls, d reads * => TranslateMemberDecl(d)); var memberNames := Seq.Map((m: E.Entity) => m.ei.name, members); - :- Need(forall nm <- memberNames :: nm.ChildOf(name), Invalid("Malformed name")); + :- Need(forall nm <- memberNames :: nm.ChildOf(name), Invalid("Malformed member name in " + name.ToString())); Success((members, E.EntityInfo(name, location := loc, attrs := attrs, members := memberNames))) } @@ -183,7 +183,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { else if tl is C.ClassDecl then TranslateClassDecl(tl) else - Failure(Invalid("Unsupported top level declaration type")); + Failure(Invalid("Unsupported top level declaration type for " + TypeConv.AsString(tl.FullDafnyName))); var topEntity := E.Entity.Type(ei, top); Success([topEntity] + members) } @@ -199,7 +199,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { assume ASTHeight(md.Signature) < ASTHeight(tl); TranslateModule(md.Signature) else - Failure(Invalid("Unsupported top level declaration type")) + Failure(Invalid("Unsupported top level declaration type for " + TypeConv.AsString(tl.FullDafnyName))) } function TranslateModule(sig: C.ModuleSignature): (m: TranslationResult>) @@ -219,7 +219,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { TranslateTopLevelDecl(tl.1)); var topDecls' := Seq.Flatten(topDecls); var topNames := Seq.Map((d: E.Entity) => d.ei.name, topDecls'); - :- Need(forall nm <- topNames :: nm.ChildOf(name), Invalid("Malformed name")); + :- Need(forall nm <- topNames :: nm.ChildOf(name), Invalid("Malformed name in " + name.ToString())); var ei := E.EntityInfo(name, location := loc, attrs := attrs, members := topNames); var mod := E.Entity.Module(ei, E.Module.Module()); Success([mod] + topDecls') From cb85ee5faaa7c4e3dc6ccefc8a77aba0df1d488b Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Mon, 29 Aug 2022 11:32:56 -0700 Subject: [PATCH 049/105] ast: Translate ensures and requires clauses Also partial support for empty definition bodies --- src/AST/Entities.dfy | 6 +++--- src/AST/Translator.Entity.dfy | 24 ++++++++++++++++++++---- src/Tools/Auditor/Auditor.dfy | 12 ++++++------ 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/src/AST/Entities.dfy b/src/AST/Entities.dfy index 22deeadf..97700d3f 100644 --- a/src/AST/Entities.dfy +++ b/src/AST/Entities.dfy @@ -59,9 +59,9 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities Field(kind: FieldKind, body: Option) datatype Callable = - | Method(body: Expr) - | Function(body: Expr) - | Constructor(body: Expr) + | Method(req: seq, ens: seq, body: Option) + | Function(req: seq, ens: seq, body: Option) + | Constructor(req: seq, ens: seq, body: Option) datatype Definition = | Field(fi: Field) diff --git a/src/AST/Translator.Entity.dfy b/src/AST/Translator.Entity.dfy index 440ac492..2c94a0b9 100644 --- a/src/AST/Translator.Entity.dfy +++ b/src/AST/Translator.Entity.dfy @@ -50,6 +50,17 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { Success([E.Attribute.Attribute(TranslateAttributeName(name), args)] + rest) } + // TODO: adapt auto-generated AST to include some nullable fields + function TranslateNullableExpression(e: C.Expression?): TranslationResult> + reads * + { + if e == null then + Success(None) + else + var e' :- Expr.TranslateExpression(e); + Success(Some(e')) + } + function TranslateMemberEntityInfo(md: C.MemberDecl): (e: TranslationResult) reads * { @@ -74,12 +85,14 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { function TranslateMethod(m: C.Method): (d: TranslationResult) reads * { + // TODO: empty body var body :- Expr.TranslateStatement(m.Body); + var req :- Seq.MapResult(ListUtils.ToSeq(m.Req), (ae: C.AttributedExpression) reads * => Expr.TranslateExpression(ae.E)); + var ens :- Seq.MapResult(ListUtils.ToSeq(m.Ens), (ae: C.AttributedExpression) reads * => Expr.TranslateExpression(ae.E)); var def := if m is C.Constructor then - E.Constructor(body) + E.Constructor(req := req, ens := ens, body := Some(body)) else - E.Method(body); - var ei := E.EntityInfo(name, attrs := attrs, members := []); + E.Method(req := req, ens := ens, body := Some(body)); var ei :- TranslateMemberEntityInfo(m); Success(E.Definition(ei, E.Callable(def))) } @@ -87,9 +100,12 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { function TranslateFunction(f: C.Function): (d: TranslationResult) reads * { + // TODO: empty body var body :- Expr.TranslateExpression(f.Body); - Success(E.Definition(ei, E.Callable(E.Function(body)))) + var req :- Seq.MapResult(ListUtils.ToSeq(f.Req), (ae: C.AttributedExpression) reads * => Expr.TranslateExpression(ae.E)); + var ens :- Seq.MapResult(ListUtils.ToSeq(f.Ens), (ae: C.AttributedExpression) reads * => Expr.TranslateExpression(ae.E)); var ei :- TranslateMemberEntityInfo(f); + Success(E.Definition(ei, E.Callable(E.Function(req := req, ens := ens, body := Some(body))))) } function TranslateMemberDecl(md: C.MemberDecl): (d: TranslationResult) diff --git a/src/Tools/Auditor/Auditor.dfy b/src/Tools/Auditor/Auditor.dfy index 7bc6b496..cf1b4da7 100644 --- a/src/Tools/Auditor/Auditor.dfy +++ b/src/Tools/Auditor/Auditor.dfy @@ -1,7 +1,7 @@ include "../../AST/Entities.dfy" include "../../AST/Names.dfy" include "../../AST/Syntax.dfy" -include "../../AST/Translator.Entities.dfy" +include "../../AST/Translator.Entity.dfy" include "../../Interop/CSharpDafnyASTModel.dfy" include "../../Interop/CSharpDafnyInterop.dfy" include "../../Utils/Library.dfy" @@ -10,7 +10,7 @@ include "Report.dfy" import opened Bootstrap.AST.Entities import opened Bootstrap.AST.Names import opened Bootstrap.AST.Syntax.Exprs -import opened Bootstrap.AST.EntityTranslator +import E = Bootstrap.AST.Translator.Entity import opened AuditReport import opened Utils.Lib.Datatypes import opened Utils.Lib.Seq @@ -30,9 +30,9 @@ function GetTags(e: Entity): set { TagIf(exists a | a in e.ei.attrs :: a.name == Extern, HasExternAttribute) + TagIf(exists a | a in e.ei.attrs :: a.name == Axiom, HasAxiomAttribute) + TagIf(e.Type? && e.t.SubsetType? && e.t.st.witnessExpr.None?, HasNoWitness) + - TagIf(e.Definition? && e.d.Callable? /*&& e.d.ci.body.None?*/, HasNoBody) + - //TagIf(e.Definition? && e.d.Callable? && ContainsAssumeStatement(e.d.ci.body), HasNoBody) + - TagIf(e.Definition? && e.d.Callable? /*&& e.d.ci.ensures.Some?*/, HasEnsuresClause) + TagIf(e.Definition? && e.d.Callable? && e.d.ci.body.None?, HasNoBody) + + //TagIf(e.Definition? && e.d.Callable? && ContainsAssumeStatement(e.d.ci.body), HasAssumeInBody) + + TagIf(e.Definition? && e.d.Callable? && |e.d.ci.ens| > 0, HasEnsuresClause) } //// Rport generation //// @@ -56,7 +56,7 @@ function GenerateAuditReport(reg: Registry): Report { function {:export} Audit(p: Bootstrap.Interop.CSharpDafnyASTModel.Program): string reads * { - var res := TranslateProgram(p); + var res := E.TranslateProgram(p); match res { case Success(p') => var rpt := GenerateAuditReport(p'.registry); From 5d9ec15d7562b46bda83decd7f08516247daaeab Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Mon, 29 Aug 2022 15:30:39 -0700 Subject: [PATCH 050/105] auditor: Get tool to compile Some tweaks will be needed to get the full top-level build to work, but most of the pieces are there now. --- GNUmakefile | 16 +++- src/AST/Entities.dfy | 2 +- src/Interop/CSharpInterop.cs | 6 +- src/Interop/CSharpInterop.dfy | 7 +- src/Tools/Auditor/.gitignore | 1 + src/Tools/Auditor/Auditor.dfy | 107 +++++++++++++------------- src/Tools/Auditor/DafnyAuditor.csproj | 19 +++++ src/Tools/Auditor/EntryPoint.cs | 12 ++- src/Tools/Auditor/Report.dfy | 2 +- 9 files changed, 105 insertions(+), 67 deletions(-) create mode 100644 src/Tools/Auditor/.gitignore create mode 100644 src/Tools/Auditor/DafnyAuditor.csproj diff --git a/GNUmakefile b/GNUmakefile index e6a987ed..0d2abdeb 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -58,14 +58,16 @@ dafny_verify := $(dafny) -compile:0 -trace -verifyAllModules -showSnippets:1 -v # Subprojects csharp := src/Backends/CSharp repl := src/REPL +auditor := src/Tools/Auditor # Binaries plugin_dll := $(csharp)/bin/Debug/net6.0/CSharpCompiler.dll repl_dll := $(repl)/bin/Release/net6.0/REPL.dll -dlls := $(plugin_dll) $(repl_dll) +auditor_dll := $(auditor)/bin/Release/net6.0/DafnyAuditor.dll +dlls := $(plugin_dll) $(repl_dll) $(auditor_dll) # Entry points -dfy_entry_points := $(repl)/Repl.dfy $(csharp)/Compiler.dfy +dfy_entry_points := $(repl)/Repl.dfy $(csharp)/Compiler.dfy $(auditor)/Auditor.dfy cs_entry_points := $(dfy_entry_points:.dfy=.cs) cs_roots := $(dir $(cs_entry_points)) cs_objs := $(cs_roots:=bin) $(cs_roots:=obj) @@ -103,10 +105,20 @@ $(csharp)/Compiler.cs: $(csharp)/Compiler.dfy $(dfy_models) $(dfy_interop) $(Daf sed -i.bak -e 's/__AUTOGEN__//g' "$@" rm "$@.bak" # https://stackoverflow.com/questions/4247068/ +$(auditor)/Auditor.cs: $(auditor)/Auditor.dfy $(dfy_models) $(dfy_interop) $(DafnyRuntime) + $(dafny_codegen) $< || true + sed -i.bak -e 's/__AUTOGEN__//g' "$@" + rm "$@.bak" + sed -i.bak -e 's/.*Tuple0.*//g' "$@" + rm "$@.bak" # https://stackoverflow.com/questions/4247068/ + # Compile the resulting C# code $(plugin_dll): $(csharp)/Compiler.cs $(cs_interop) dotnet build $(csharp)/CSharpCompiler.csproj +$(auditor_dll): $(auditor)/Auditor.cs $(cs_interop) + dotnet build $(auditor)/DafnyAuditor.csproj + # Run it on tests test/%.cs: test/%.dfy $(plugin_dll) $(DafnyRuntime) $(dafny_codegen) -plugin:$(plugin_dll) -compileTarget:cs "$<" diff --git a/src/AST/Entities.dfy b/src/AST/Entities.dfy index 97700d3f..79c19166 100644 --- a/src/AST/Entities.dfy +++ b/src/AST/Entities.dfy @@ -506,7 +506,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities [root] + Seq.Flatten(Seq.Map(RecursiveTransitiveMembers'(root), Members(root))) } - function RecursiveTransitiveMembers'(ghost root: Name) + function RecursiveTransitiveMembers'(root: Name) : (Name --> seq) requires Valid?() requires Contains(root) diff --git a/src/Interop/CSharpInterop.cs b/src/Interop/CSharpInterop.cs index 9030ce6e..0efc1ab1 100644 --- a/src/Interop/CSharpInterop.cs +++ b/src/Interop/CSharpInterop.cs @@ -21,10 +21,10 @@ public static B FoldR(Func f, B b0, List lA) { } } - public class DictUtils { - public static R FoldL(Func f, R r0, Dictionary d) { + public partial class DictUtils { + public static R FoldL(Func f, R r0, Dictionary d) where K : notnull { foreach (var k in d.Keys) { - r0 = f(r0, (k, d[k])); + r0 = f(r0, k, d[k]); } return r0; } diff --git a/src/Interop/CSharpInterop.dfy b/src/Interop/CSharpInterop.dfy index 18020794..7e329488 100644 --- a/src/Interop/CSharpInterop.dfy +++ b/src/Interop/CSharpInterop.dfy @@ -28,11 +28,12 @@ module {:extern "CSharpInterop"} Bootstrap.Interop.CSharpInterop { class DictUtils { constructor {:extern} () requires false // Prevent instantiation - static function method {:extern} FoldL(f: (R, (K, V)) -> R, r0: R, d: Dictionary): R + static function method {:extern} FoldL(f: (R, K, V) -> R, r0: R, d: Dictionary): R - static function method ReduceSeq(acc: seq<(K, V)>, entry: (K, V)): seq<(K, V)> { - acc + [entry] + static function method ReduceSeq(acc: seq<(K, V)>, key: K, value: V): seq<(K, V)> { + acc + [(key, value)] } + static function method DictionaryToSeq(d: Dictionary): seq<(K, V)> { FoldL(ReduceSeq, [], d) } diff --git a/src/Tools/Auditor/.gitignore b/src/Tools/Auditor/.gitignore new file mode 100644 index 00000000..eec878bc --- /dev/null +++ b/src/Tools/Auditor/.gitignore @@ -0,0 +1 @@ +Auditor.cs diff --git a/src/Tools/Auditor/Auditor.dfy b/src/Tools/Auditor/Auditor.dfy index cf1b4da7..ab4a2682 100644 --- a/src/Tools/Auditor/Auditor.dfy +++ b/src/Tools/Auditor/Auditor.dfy @@ -7,70 +7,69 @@ include "../../Interop/CSharpDafnyInterop.dfy" include "../../Utils/Library.dfy" include "Report.dfy" -import opened Bootstrap.AST.Entities -import opened Bootstrap.AST.Names -import opened Bootstrap.AST.Syntax.Exprs -import E = Bootstrap.AST.Translator.Entity -import opened AuditReport -import opened Utils.Lib.Datatypes -import opened Utils.Lib.Seq +module {:extern "Bootstrap.Tools.Auditor"} {:options "-functionSyntax:4"} Bootstrap.Tools.Auditor { -//// AST traversals //// + import opened AST.Entities + import opened AST.Names + import opened AST.Syntax.Exprs + import E = AST.Translator.Entity + import opened AuditReport + import opened Interop + import opened Utils.Lib.Datatypes + import opened Utils.Lib.Seq -// TODO: can't be implemented yet because there's no representation for `assume` -//predicate ContainsAssumeStatement(e: Expr) + //// AST traversals //// -//// Tag extraction and processing //// + // TODO: can't be implemented yet because there's no representation for `assume` + //predicate ContainsAssumeStatement(e: Expr) -function TagIf(cond: bool, t: Tag): set { - if cond then {t} else {} -} + //// Tag extraction and processing //// -function GetTags(e: Entity): set { - TagIf(exists a | a in e.ei.attrs :: a.name == Extern, HasExternAttribute) + - TagIf(exists a | a in e.ei.attrs :: a.name == Axiom, HasAxiomAttribute) + - TagIf(e.Type? && e.t.SubsetType? && e.t.st.witnessExpr.None?, HasNoWitness) + - TagIf(e.Definition? && e.d.Callable? && e.d.ci.body.None?, HasNoBody) + - //TagIf(e.Definition? && e.d.Callable? && ContainsAssumeStatement(e.d.ci.body), HasAssumeInBody) + - TagIf(e.Definition? && e.d.Callable? && |e.d.ci.ens| > 0, HasEnsuresClause) -} - -//// Rport generation //// - -function AddAssumptions(e: Entity, rpt: Report): Report { - var tags := GetTags(e); - if IsAssumption(tags) - then Report(rpt.assumptions + [Assumption(e.ei.name.ToString(), tags)]) - else rpt -} + function TagIf(cond: bool, t: Tag): set { + if cond then {t} else {} + } -function FoldEntities(f: (Entity, T) -> T, reg: Registry, init: T): T { - var names := reg.SortedNames(); - FoldL((a, n) requires reg.Contains(n) => f(reg.Get(n), a), init, names) -} + function GetTags(e: Entity): set { + TagIf(exists a | a in e.ei.attrs :: a.name == Extern, HasExternAttribute) + + TagIf(exists a | a in e.ei.attrs :: a.name == Axiom, HasAxiomAttribute) + + TagIf(e.Type? && e.t.SubsetType? && e.t.st.witnessExpr.None?, HasNoWitness) + + TagIf(e.Definition? && e.d.Callable? && e.d.ci.body.None?, HasNoBody) + + //TagIf(e.Definition? && e.d.Callable? && ContainsAssumeStatement(e.d.ci.body), HasAssumeInBody) + + TagIf(e.Definition? && e.d.Callable? && |e.d.ci.ens| > 0, HasEnsuresClause) + } -function GenerateAuditReport(reg: Registry): Report { - FoldEntities(AddAssumptions, reg, EmptyReport) -} + //// Report generation //// -function {:export} Audit(p: Bootstrap.Interop.CSharpDafnyASTModel.Program): string - reads * -{ - var res := E.TranslateProgram(p); - match res { - case Success(p') => - var rpt := GenerateAuditReport(p'.registry); - RenderAuditReportMarkdown(rpt) - case Failure(err) => err.ToString() + function AddAssumptions(e: Entity, rpt: Report): Report { + var tags := GetTags(e); + if IsAssumption(tags) + then Report(rpt.assumptions + [Assumption(e.ei.name.ToString(), tags)]) + else rpt } -} -//// Later functionality //// + function FoldEntities(f: (Entity, T) -> T, reg: Registry, init: T): T { + var names := reg.SortedNames(); + FoldL((a, n) requires reg.Contains(n) => f(reg.Get(n), a), init, names) + } -/* -predicate EntityDependsOn(client: Entity, target: Entity) + function GenerateAuditReport(reg: Registry): Report { + FoldEntities(AddAssumptions, reg, EmptyReport) + } -function ImmediateDependents(e: Entity): set + class {:extern} DafnyAuditor { + constructor() { + } -function TransitiveDependents(e: Entity): set -*/ + function Audit(p: CSharpDafnyASTModel.Program): string + reads * + { + var res := E.TranslateProgram(p); + match res { + case Success(p') => + var rpt := GenerateAuditReport(p'.registry); + RenderAuditReportMarkdown(rpt) + case Failure(err) => err.ToString() + } + } + } +} \ No newline at end of file diff --git a/src/Tools/Auditor/DafnyAuditor.csproj b/src/Tools/Auditor/DafnyAuditor.csproj new file mode 100644 index 00000000..fa42c317 --- /dev/null +++ b/src/Tools/Auditor/DafnyAuditor.csproj @@ -0,0 +1,19 @@ + + + false + net6.0 + enable + enable + + + + ..\..\..\..\ + + + + + + + + + diff --git a/src/Tools/Auditor/EntryPoint.cs b/src/Tools/Auditor/EntryPoint.cs index 4c747943..573fb686 100644 --- a/src/Tools/Auditor/EntryPoint.cs +++ b/src/Tools/Auditor/EntryPoint.cs @@ -1,9 +1,15 @@ -namespace Microsoft.Dafny; +using System; +using Bootstrap.Tools.Auditor; + +namespace Microsoft.Dafny.Compilers.SelfHosting.Auditor; + +public class Auditor : Plugins.Rewriter { -public class AuditorPlugin : IRewriter { private readonly DafnyAuditor auditor = new(); - internal override void PostResolve(Program program) { + internal Auditor(ErrorReporter reporter) : base(reporter) { } + + public override void PostResolve(Program program) { var text = auditor.Audit(program); // TODO: write to the console or file depending on options Console.WriteLine(text); diff --git a/src/Tools/Auditor/Report.dfy b/src/Tools/Auditor/Report.dfy index 94e14ac1..1b0aeacc 100644 --- a/src/Tools/Auditor/Report.dfy +++ b/src/Tools/Auditor/Report.dfy @@ -48,7 +48,7 @@ module AuditReport { //// Tag categorization //// - predicate IsAssumption(ts: set) { + predicate method IsAssumption(ts: set) { || (IsSubsetType in ts && HasNoWitness in ts) || HasAxiomAttribute in ts || (&& IsCallable in ts From 9fa625947e74110133676e2fa96857af85c37757 Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Tue, 30 Aug 2022 14:53:30 -0700 Subject: [PATCH 051/105] build: Add missing dependency for auditor --- GNUmakefile | 6 +++--- src/Tools/Auditor/EntryPoint.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index 0d2abdeb..2a27665e 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -47,7 +47,7 @@ DafnyPipeline = $(dafny_Source)/Dafny/DafnyPipeline DafnyAST = $(dafny_Source)/Dafny/AST/DafnyAst DafnyRuntime := $(dafny_Source)/DafnyRuntime/DafnyRuntime.cs -dafny := dotnet run --project $(DafnyDriver).csproj $(DAFNY_DOTNET_RUN_FLAGS) -- +dafny ?= dotnet run --project $(DafnyDriver).csproj $(DAFNY_DOTNET_RUN_FLAGS) -- dafny_codegen := $(dafny) -spillTargetCode:3 -compile:0 -noVerify -useRuntimeLib dafny_typecheck := $(dafny) -dafnyVerify:0 dafny_verify := $(dafny) -compile:0 -trace -verifyAllModules -showSnippets:1 -vcsCores:8 @@ -63,7 +63,7 @@ auditor := src/Tools/Auditor # Binaries plugin_dll := $(csharp)/bin/Debug/net6.0/CSharpCompiler.dll repl_dll := $(repl)/bin/Release/net6.0/REPL.dll -auditor_dll := $(auditor)/bin/Release/net6.0/DafnyAuditor.dll +auditor_dll := $(auditor)/bin/Debug/net6.0/DafnyAuditor.dll dlls := $(plugin_dll) $(repl_dll) $(auditor_dll) # Entry points @@ -116,7 +116,7 @@ $(auditor)/Auditor.cs: $(auditor)/Auditor.dfy $(dfy_models) $(dfy_interop) $(Daf $(plugin_dll): $(csharp)/Compiler.cs $(cs_interop) dotnet build $(csharp)/CSharpCompiler.csproj -$(auditor_dll): $(auditor)/Auditor.cs $(cs_interop) +$(auditor_dll): $(auditor)/Auditor.cs $(auditor)/EntryPoint.cs $(cs_interop) dotnet build $(auditor)/DafnyAuditor.csproj # Run it on tests diff --git a/src/Tools/Auditor/EntryPoint.cs b/src/Tools/Auditor/EntryPoint.cs index 573fb686..0ee1ff55 100644 --- a/src/Tools/Auditor/EntryPoint.cs +++ b/src/Tools/Auditor/EntryPoint.cs @@ -7,7 +7,7 @@ public class Auditor : Plugins.Rewriter { private readonly DafnyAuditor auditor = new(); - internal Auditor(ErrorReporter reporter) : base(reporter) { } + public Auditor(ErrorReporter reporter) : base(reporter) { } public override void PostResolve(Program program) { var text = auditor.Audit(program); From 1ca43a183b73eb59f4931d46f58cabbc25c733ff Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Tue, 30 Aug 2022 14:58:02 -0700 Subject: [PATCH 052/105] ast: Re-add `Unsupported` expression alternative --- src/AST/Syntax.dfy | 6 ++++++ src/AST/Translator.Expressions.dfy | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/AST/Syntax.dfy b/src/AST/Syntax.dfy index 00ad50be..3ff1d3d7 100644 --- a/src/AST/Syntax.dfy +++ b/src/AST/Syntax.dfy @@ -250,6 +250,7 @@ module Exprs { | Block(stmts: seq) | Bind(vars: seq, vals: seq, body: Expr) | If(cond: Expr, thn: Expr, els: Expr) // DISCUSS: Lazy op node? + | Unsupported(description: string) { function method Depth() : nat { 1 + match this { @@ -270,6 +271,8 @@ module Exprs { ) case If(cond, thn, els) => Math.Max(cond.Depth(), Math.Max(thn.Depth(), els.Depth())) + case Unsupported(_) => + 0 } } @@ -284,6 +287,7 @@ module Exprs { case Block(exprs) => exprs case Bind(vars, vals, body) => vals + [body] case If(cond, thn, els) => [cond, thn, els] + case Unsupported(_) => [] } } } @@ -324,6 +328,8 @@ module Exprs { e'.If? case Bind(vars, vals, body) => e'.Bind? && |vars| == |e'.vars| && |vals| == |e'.vals| + case Unsupported(description) => + e'.Unsupported? && e'.description == description } } diff --git a/src/AST/Translator.Expressions.dfy b/src/AST/Translator.Expressions.dfy index 417c49b0..8607e329 100644 --- a/src/AST/Translator.Expressions.dfy +++ b/src/AST/Translator.Expressions.dfy @@ -484,7 +484,7 @@ module Bootstrap.AST.Translator.Expressions { TranslateITEExpr(c as C.ITEExpr) else if c is C.ConcreteSyntaxExpression then TranslateConcreteSyntaxExpression(c as C.ConcreteSyntaxExpression) - else Failure(UnsupportedExpr(c)) + else Success(DE.Unsupported("Unsupported expression")) } function method TranslatePrintStmt(p: C.PrintStmt) @@ -533,7 +533,7 @@ module Bootstrap.AST.Translator.Expressions { TranslateBlockStmt(s as C.BlockStmt) else if s is C.IfStmt then TranslateIfStmt(s as C.IfStmt) - else Failure(UnsupportedStmt(s)) + else Success(DE.Unsupported("Unsupported statement")) } /* From 08a7e77d5bf147c2bd607c7d334264c71f4f8e8d Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Tue, 30 Aug 2022 14:59:52 -0700 Subject: [PATCH 053/105] ast: Add support for predicate statements --- src/AST/Syntax.dfy | 6 ++++++ src/AST/Translator.Expressions.dfy | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/AST/Syntax.dfy b/src/AST/Syntax.dfy index 3ff1d3d7..115413b6 100644 --- a/src/AST/Syntax.dfy +++ b/src/AST/Syntax.dfy @@ -200,8 +200,14 @@ module Exprs { function method Depth() : nat { 1 } } + datatype PredicateType = + | Assert + | Assume + | Expect + datatype BuiltinFunction = | Display(ty: Types.Type) + | Predicate(predTy: PredicateType) | Print // DafnyAst.cs handles `f(1)` differently from `(var g := f; g)(1)`, but not us diff --git a/src/AST/Translator.Expressions.dfy b/src/AST/Translator.Expressions.dfy index 8607e329..45d40532 100644 --- a/src/AST/Translator.Expressions.dfy +++ b/src/AST/Translator.Expressions.dfy @@ -522,6 +522,22 @@ module Bootstrap.AST.Translator.Expressions { Success(DE.If(cond, thn, els)) } + function method TranslatePredicateStmt(p: C.PredicateStmt) + : (e: TranslationResult) + reads * + { + var predTy :- if p is C.AssertStmt then + Success(DE.Assert) + else if p is C.AssumeStmt then + Success(DE.Assume) + else if p is C.ExpectStmt then + Success(DE.Expect) + else + Failure(UnsupportedStmt(p)); + var e :- TranslateExpression(p.Expr); + Success(DE.Apply(DE.Eager(DE.Builtin(DE.BuiltinFunction.Predicate(predTy))), [e])) + } + function method TranslateStatement(s: C.Statement) : TranslationResult reads * @@ -533,6 +549,8 @@ module Bootstrap.AST.Translator.Expressions { TranslateBlockStmt(s as C.BlockStmt) else if s is C.IfStmt then TranslateIfStmt(s as C.IfStmt) + else if s is C.PredicateStmt then + TranslatePredicateStmt(s as C.PredicateStmt) else Success(DE.Unsupported("Unsupported statement")) } From e6105272ea4b292016e2e7f660ed475487d85f89 Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Tue, 30 Aug 2022 15:03:45 -0700 Subject: [PATCH 054/105] ast: Translate empty method and function bodies --- src/AST/Translator.Entity.dfy | 12 +++++------ src/AST/Translator.Expressions.dfy | 34 ++++++++++++++++-------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/AST/Translator.Entity.dfy b/src/AST/Translator.Entity.dfy index 2c94a0b9..3d7df355 100644 --- a/src/AST/Translator.Entity.dfy +++ b/src/AST/Translator.Entity.dfy @@ -85,14 +85,13 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { function TranslateMethod(m: C.Method): (d: TranslationResult) reads * { - // TODO: empty body - var body :- Expr.TranslateStatement(m.Body); + var body :- Expr.TranslateOptionalStatement(m.Body); var req :- Seq.MapResult(ListUtils.ToSeq(m.Req), (ae: C.AttributedExpression) reads * => Expr.TranslateExpression(ae.E)); var ens :- Seq.MapResult(ListUtils.ToSeq(m.Ens), (ae: C.AttributedExpression) reads * => Expr.TranslateExpression(ae.E)); var def := if m is C.Constructor then - E.Constructor(req := req, ens := ens, body := Some(body)) + E.Constructor(req := req, ens := ens, body := body) else - E.Method(req := req, ens := ens, body := Some(body)); + E.Method(req := req, ens := ens, body := body); var ei :- TranslateMemberEntityInfo(m); Success(E.Definition(ei, E.Callable(def))) } @@ -100,12 +99,11 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { function TranslateFunction(f: C.Function): (d: TranslationResult) reads * { - // TODO: empty body - var body :- Expr.TranslateExpression(f.Body); + var body :- Expr.TranslateOptionalExpression(f.Body); var req :- Seq.MapResult(ListUtils.ToSeq(f.Req), (ae: C.AttributedExpression) reads * => Expr.TranslateExpression(ae.E)); var ens :- Seq.MapResult(ListUtils.ToSeq(f.Ens), (ae: C.AttributedExpression) reads * => Expr.TranslateExpression(ae.E)); var ei :- TranslateMemberEntityInfo(f); - Success(E.Definition(ei, E.Callable(E.Function(req := req, ens := ens, body := Some(body))))) + Success(E.Definition(ei, E.Callable(E.Function(req := req, ens := ens, body := body)))) } function TranslateMemberDecl(md: C.MemberDecl): (d: TranslationResult) diff --git a/src/AST/Translator.Expressions.dfy b/src/AST/Translator.Expressions.dfy index 45d40532..b76bfe9a 100644 --- a/src/AST/Translator.Expressions.dfy +++ b/src/AST/Translator.Expressions.dfy @@ -487,6 +487,17 @@ module Bootstrap.AST.Translator.Expressions { else Success(DE.Unsupported("Unsupported expression")) } + // TODO: adapt auto-generated AST to include some nullable fields + function method TranslateOptionalExpression(e: C.Expression): TranslationResult> + reads * + { + if e == null then + Success(None) + else + var e' :- TranslateExpression(e); + Success(Some(e')) + } + function method TranslatePrintStmt(p: C.PrintStmt) : (e: TranslationResult) reads * @@ -554,23 +565,14 @@ module Bootstrap.AST.Translator.Expressions { else Success(DE.Unsupported("Unsupported statement")) } - /* - function method TranslateMethod(m: C.Method) - : TranslationResult + // TODO: adapt auto-generated AST to include some nullable fields + function method TranslateOptionalStatement(s: C.Statement): TranslationResult> reads * { - // var compileName := m.CompileName; - // FIXME “Main” - var stmts :- Seq.MapResult(ListUtils.ToSeq(m.Body.Body), TranslateStatement); - Success(D.Method("Main", DE.Block(stmts))) - } - - function method TranslateProgram(p: C.Program) - : TranslationResult - reads * - { - var tm :- TranslateMethod(p.MainMethod); - Success(D.Program(tm)) + if s == null then + Success(None) + else + var s' :- TranslateStatement(s); + Success(Some(s')) } - */ } From b7dc952c2094b3dfc514923a0811faf64c215006 Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Tue, 30 Aug 2022 15:11:17 -0700 Subject: [PATCH 055/105] ast: Allow empty names, locations, and attributes --- src/AST/Translator.Entity.dfy | 41 ++++++++++++--------------- src/Interop/CSharpDafnyASTInterop.dfy | 1 + src/Interop/CSharpInterop.cs | 9 ++++++ 3 files changed, 28 insertions(+), 23 deletions(-) diff --git a/src/AST/Translator.Entity.dfy b/src/AST/Translator.Entity.dfy index 3d7df355..cfddec7c 100644 --- a/src/AST/Translator.Entity.dfy +++ b/src/AST/Translator.Entity.dfy @@ -14,18 +14,21 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { function TranslateName(str: System.String): TranslationResult { var name := TypeConv.AsString(str); - var parts := Seq.Split('.', name); - :- Need(forall s | s in parts :: s != "", Invalid("Empty component in name: " + name)); - assert forall s | s in parts :: '.' !in s; - assert forall s | s in parts :: s != "" && '.' !in s; - var atoms : seq := parts; - Success(Seq.FoldL((n: N.Name, a: N.Atom) => N.Name(n, a), N.Anonymous, atoms)) + if name == "" then + Success(N.Anonymous) + else + var parts := Seq.Split('.', name); + :- Need(forall s | s in parts :: s != "", Invalid("Empty component in name: " + name)); + assert forall s | s in parts :: '.' !in s; + assert forall s | s in parts :: s != "" && '.' !in s; + var atoms : seq := parts; + Success(Seq.FoldL((n: N.Name, a: N.Atom) => N.Name(n, a), N.Anonymous, atoms)) } function TranslateLocation(tok: Microsoft.Boogie.IToken): E.Location reads * { - var filename := TypeConv.AsString(tok.FileName); + var filename := if tok.FileName == null then "" else TypeConv.AsString(tok.FileName); var line := tok.Line; var col := tok.Column; E.Location(filename, line as int, col as int) @@ -41,24 +44,16 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { function TranslateAttributes(attrs: C.Attributes): TranslationResult> reads * + decreases ASTHeight(attrs) { - var name := TypeConv.AsString(attrs.Name); - var args :- Seq.MapResult(ListUtils.ToSeq(attrs.Args), Expr.TranslateExpression); - // TODO: Attributes needs to be nullable - //var rest :- if attrs.Prev == null then Success([]) else TranslateAttributes(attrs.Prev); - var rest := []; - Success([E.Attribute.Attribute(TranslateAttributeName(name), args)] + rest) - } - - // TODO: adapt auto-generated AST to include some nullable fields - function TranslateNullableExpression(e: C.Expression?): TranslationResult> - reads * - { - if e == null then - Success(None) + if attrs == null then + Success([]) else - var e' :- Expr.TranslateExpression(e); - Success(Some(e')) + var name := TypeConv.AsString(attrs.Name); + var args :- Seq.MapResult(ListUtils.ToSeq(attrs.Args), Expr.TranslateExpression); + assume ASTHeight(attrs.Prev) < ASTHeight(attrs); + var rest :- TranslateAttributes(attrs.Prev); + Success([E.Attribute.Attribute(TranslateAttributeName(name), args)] + rest) } function TranslateMemberEntityInfo(md: C.MemberDecl): (e: TranslationResult) diff --git a/src/Interop/CSharpDafnyASTInterop.dfy b/src/Interop/CSharpDafnyASTInterop.dfy index f73dc5a5..e1a190a2 100644 --- a/src/Interop/CSharpDafnyASTInterop.dfy +++ b/src/Interop/CSharpDafnyASTInterop.dfy @@ -10,6 +10,7 @@ module {:extern "CSharpDafnyASTInterop"} Bootstrap.Interop.CSharpDafnyASTInterop || c is CSharpDafnyASTModel.Statement || c is CSharpDafnyASTModel.Declaration || c is CSharpDafnyASTModel.ModuleSignature + || c is CSharpDafnyASTModel.Attributes class {:extern} TypeUtils { constructor {:extern} () requires false // Prevent instantiation diff --git a/src/Interop/CSharpInterop.cs b/src/Interop/CSharpInterop.cs index 0efc1ab1..53a58e1a 100644 --- a/src/Interop/CSharpInterop.cs +++ b/src/Interop/CSharpInterop.cs @@ -7,6 +7,9 @@ public partial class ListUtils { public static void Append(List l, T t) => l.Add(t); public static B FoldL(Func f, B b0, List lA) { + if(lA is null) { + return b0; + } for (int i = 0; i < lA.Count; i++) { b0 = f(b0, lA[i]); } @@ -14,6 +17,9 @@ public static B FoldL(Func f, B b0, List lA) { } public static B FoldR(Func f, B b0, List lA) { + if(lA is null) { + return b0; + } for (int i = lA.Count - 1; i >= 0; i--) { b0 = f(lA[i], b0); } @@ -23,6 +29,9 @@ public static B FoldR(Func f, B b0, List lA) { public partial class DictUtils { public static R FoldL(Func f, R r0, Dictionary d) where K : notnull { + if(d is null) { + return r0; + } foreach (var k in d.Keys) { r0 = f(r0, k, d[k]); } From ec6fa5b47ecd45b57f0de8329a01d3e0dce5d290 Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Tue, 30 Aug 2022 15:13:54 -0700 Subject: [PATCH 056/105] ast: Allow conversion expressions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit They’re translated to `Unsupported`, though. --- src/AST/Translator.Expressions.dfy | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/AST/Translator.Expressions.dfy b/src/AST/Translator.Expressions.dfy index b76bfe9a..20785e84 100644 --- a/src/AST/Translator.Expressions.dfy +++ b/src/AST/Translator.Expressions.dfy @@ -454,6 +454,8 @@ module Bootstrap.AST.Translator.Expressions { { if c is C.IdentifierExpr then TranslateIdentifierExpr(c as C.IdentifierExpr) + else if c is C.ConversionExpr then + Success(DE.Unsupported("conversion expr")) else if c is C.UnaryExpr then TranslateUnary(c as C.UnaryExpr) else if c is C.BinaryExpr then From 550974eab17713795c11608972c6ca276ef592cb Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Tue, 30 Aug 2022 15:14:34 -0700 Subject: [PATCH 057/105] ast: Provide `ToString` function for `ValidationError` --- src/AST/Entities.dfy | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/AST/Entities.dfy b/src/AST/Entities.dfy index 79c19166..25730b58 100644 --- a/src/AST/Entities.dfy +++ b/src/AST/Entities.dfy @@ -59,6 +59,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities Field(kind: FieldKind, body: Option) datatype Callable = + // TODO: should all these fields be part of Callable, instead? | Method(req: seq, ens: seq, body: Option) | Function(req: seq, ens: seq, body: Option) | Constructor(req: seq, ens: seq, body: Option) @@ -154,6 +155,15 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities | NameMismatch(name: Name, key: Name) | UnboundMember(name: Name, member: Name) | UnboundParent(name: Name, parent: Name) + { + function ToString(): string { + match this { + case NameMismatch(name, key) => "Name mismatch: " + name.ToString() + " and " + key.ToString() + case UnboundMember(name, parent) => "Unbound member: " + name.ToString() + " in " + parent.ToString() + case UnboundParent(name, parent) => "Unbound parent: " + name.ToString() + " in " + parent.ToString() + } + } + } type Registry = r: Registry_ | r.Valid?() witness Registry_.EMPTY() From 37520793a055ed0b390cd65aa0bba8f6aa8da455 Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Tue, 30 Aug 2022 15:15:04 -0700 Subject: [PATCH 058/105] auditor: Allow auditing programs that don't validate --- src/AST/Translator.Entity.dfy | 26 ++++++++++++++++++++------ src/Tools/Auditor/Auditor.dfy | 8 ++++---- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/AST/Translator.Entity.dfy b/src/AST/Translator.Entity.dfy index cfddec7c..cf0b8f9e 100644 --- a/src/AST/Translator.Entity.dfy +++ b/src/AST/Translator.Entity.dfy @@ -228,24 +228,38 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { TranslateTopLevelDecl(tl.1)); var topDecls' := Seq.Flatten(topDecls); var topNames := Seq.Map((d: E.Entity) => d.ei.name, topDecls'); - :- Need(forall nm <- topNames :: nm.ChildOf(name), Invalid("Malformed name in " + name.ToString())); + assume forall nm <- topNames :: nm.ChildOf(name); + //:- Need(forall nm <- topNames :: nm.ChildOf(name), Invalid("Malformed name in " + name.ToString())); var ei := E.EntityInfo(name, location := loc, attrs := attrs, members := topNames); var mod := E.Entity.Module(ei, E.Module.Module()); Success([mod] + topDecls') } - function TranslateProgram(p: C.Program): (exps: TranslationResult) + // TODO: generate a valid program with a validated registry + function TranslateProgram(p: C.Program): (exps: TranslationResult) reads * { var moduleSigs := DictUtils.DictionaryToSeq(p.ModuleSigs); var entities :- Seq.MapResult(moduleSigs, (sig: (C.ModuleDefinition, C.ModuleSignature)) reads * => TranslateModule(sig.1)); var regMap := Seq.FoldL((m:map, e: E.Entity) => m + map[e.ei.name := e], map[], Seq.Flatten(entities)); - var mainMethodName :- TranslateName(p.MainMethod.FullDafnyName); + var mainMethodName :- if p.MainMethod == null then + Success(None) + else + var methodName :- TranslateName(p.MainMethod.FullDafnyName); + Success(Some(methodName)); var defaultModuleName :- TranslateName(p.DefaultModule.FullDafnyName); var reg := E.Registry_.Registry(regMap); - :- Need(reg.Validate().Pass?, Invalid("Failed to validate registry")); - var prog := E.Program(reg, defaultModule := defaultModuleName, mainMethod := Some(mainMethodName)); - if prog.Valid?() then Success(prog) else Failure(Invalid("Generated invalid program")) + Success(reg) + /* + match reg.Validate() { + case Pass => + var prog := E.Program(reg, defaultModule := defaultModuleName, mainMethod := mainMethodName); + if prog.Valid?() then Success(prog) else Failure(Invalid("Generated invalid program")) + case Fail(errs) => + var err := Seq.Flatten(Seq.Map((e: E.ValidationError) => e.ToString(), errs)); + Failure(Invalid("Failed to validate registry: " + err)) + } + */ } } \ No newline at end of file diff --git a/src/Tools/Auditor/Auditor.dfy b/src/Tools/Auditor/Auditor.dfy index ab4a2682..df281b2f 100644 --- a/src/Tools/Auditor/Auditor.dfy +++ b/src/Tools/Auditor/Auditor.dfy @@ -47,12 +47,12 @@ module {:extern "Bootstrap.Tools.Auditor"} {:options "-functionSyntax:4"} Bootst else rpt } - function FoldEntities(f: (Entity, T) -> T, reg: Registry, init: T): T { + function FoldEntities(f: (Entity, T) -> T, reg: Registry_, init: T): T { var names := reg.SortedNames(); FoldL((a, n) requires reg.Contains(n) => f(reg.Get(n), a), init, names) } - function GenerateAuditReport(reg: Registry): Report { + function GenerateAuditReport(reg: Registry_): Report { FoldEntities(AddAssumptions, reg, EmptyReport) } @@ -65,8 +65,8 @@ module {:extern "Bootstrap.Tools.Auditor"} {:options "-functionSyntax:4"} Bootst { var res := E.TranslateProgram(p); match res { - case Success(p') => - var rpt := GenerateAuditReport(p'.registry); + case Success(reg) => + var rpt := GenerateAuditReport(reg); RenderAuditReportMarkdown(rpt) case Failure(err) => err.ToString() } From 641c88ac56ef5bbf8acf5c3d7382e5e5fc5bd3e0 Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Tue, 30 Aug 2022 15:58:49 -0700 Subject: [PATCH 059/105] ast: Add `Any_Expr` predicate Also add cases for `Unsupported` --- src/AST/Predicates.dfy | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/AST/Predicates.dfy b/src/AST/Predicates.dfy index fdb96c3a..897b0f87 100644 --- a/src/AST/Predicates.dfy +++ b/src/AST/Predicates.dfy @@ -41,6 +41,13 @@ abstract module Base { : (b: bool) decreases e.Depth(), 1 + function method Any_Expr(e: Expr, P: Expr -> bool) + : (b: bool) + decreases e.Depth(), 1 + { + !All_Expr(e, e' => !P(e')) + } + /* function method All_Method(m: Method, P: Expr -> bool) : bool { Shallow.All_Method(m, e => All_Expr(e, P)) @@ -106,6 +113,7 @@ abstract module Base { && All_Expr(body, P) case If(cond, thn, els) => All_Expr(cond, P) && All_Expr(thn, P) && All_Expr(els, P) + case Unsupported(_) => true } } @@ -123,6 +131,7 @@ abstract module Base { && All_Expr(body, P) case If(cond, thn, els) => All_Expr(cond, P) && All_Expr(thn, P) && All_Expr(els, P) + case Unsupported(_) => true } } From 634c6755cc6cf1e23ac8b026a5b06cbd58e25d1d Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Tue, 30 Aug 2022 16:00:11 -0700 Subject: [PATCH 060/105] auditor: Make entry point a method --- src/Tools/Auditor/Auditor.dfy | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Tools/Auditor/Auditor.dfy b/src/Tools/Auditor/Auditor.dfy index df281b2f..5eadac5c 100644 --- a/src/Tools/Auditor/Auditor.dfy +++ b/src/Tools/Auditor/Auditor.dfy @@ -60,15 +60,15 @@ module {:extern "Bootstrap.Tools.Auditor"} {:options "-functionSyntax:4"} Bootst constructor() { } - function Audit(p: CSharpDafnyASTModel.Program): string - reads * + method Audit(p: CSharpDafnyASTModel.Program) returns (r: string) { var res := E.TranslateProgram(p); match res { case Success(reg) => var rpt := GenerateAuditReport(reg); - RenderAuditReportMarkdown(rpt) - case Failure(err) => err.ToString() + return RenderAuditReportMarkdown(rpt); + case Failure(err) => + return err.ToString(); } } } From 78ebe02cf6a93e9e09a8c0c8b06565fc490fc698 Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Tue, 30 Aug 2022 16:01:42 -0700 Subject: [PATCH 061/105] auditor: Improve detection of assumptions It now traverses bodies to look for assume statements and has additional rules for identifying assumptions. --- src/Tools/Auditor/Auditor.dfy | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/Tools/Auditor/Auditor.dfy b/src/Tools/Auditor/Auditor.dfy index 5eadac5c..d3a1c98c 100644 --- a/src/Tools/Auditor/Auditor.dfy +++ b/src/Tools/Auditor/Auditor.dfy @@ -11,6 +11,7 @@ module {:extern "Bootstrap.Tools.Auditor"} {:options "-functionSyntax:4"} Bootst import opened AST.Entities import opened AST.Names + import opened AST.Predicates import opened AST.Syntax.Exprs import E = AST.Translator.Entity import opened AuditReport @@ -21,7 +22,17 @@ module {:extern "Bootstrap.Tools.Auditor"} {:options "-functionSyntax:4"} Bootst //// AST traversals //// // TODO: can't be implemented yet because there's no representation for `assume` - //predicate ContainsAssumeStatement(e: Expr) + predicate IsAssumeStatement(e: Expr) { + && e.Apply? + && e.aop.Eager? + && e.aop.eOp.Builtin? + && e.aop.eOp.builtin.Predicate? + && e.aop.eOp.builtin.predTy.Assume? + } + + predicate ContainsAssumeStatement(e: Expr) { + Deep.Any_Expr(e, IsAssumeStatement) + } //// Tag extraction and processing //// @@ -34,8 +45,11 @@ module {:extern "Bootstrap.Tools.Auditor"} {:options "-functionSyntax:4"} Bootst TagIf(exists a | a in e.ei.attrs :: a.name == Axiom, HasAxiomAttribute) + TagIf(e.Type? && e.t.SubsetType? && e.t.st.witnessExpr.None?, HasNoWitness) + TagIf(e.Definition? && e.d.Callable? && e.d.ci.body.None?, HasNoBody) + - //TagIf(e.Definition? && e.d.Callable? && ContainsAssumeStatement(e.d.ci.body), HasAssumeInBody) + - TagIf(e.Definition? && e.d.Callable? && |e.d.ci.ens| > 0, HasEnsuresClause) + TagIf(e.Definition? && e.d.Callable? && e.d.ci.body.Some? && + ContainsAssumeStatement(e.d.ci.body.value), HasAssumeInBody) + + TagIf(e.Definition? && e.d.Callable? && |e.d.ci.ens| > 0, HasEnsuresClause) + + // TagIf(e.Definition? && e.d.Callable? && |e.d.ci.req| > 0, HasRequiresClause) + + TagIf(e.Definition? && e.d.Callable?, IsCallable) } //// Report generation //// From f65817f5460441cfdf7281ace3b80f1bd066f38b Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Wed, 31 Aug 2022 15:56:56 -0700 Subject: [PATCH 062/105] ; auditor: Remove obsolete comment --- src/Tools/Auditor/Auditor.dfy | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Tools/Auditor/Auditor.dfy b/src/Tools/Auditor/Auditor.dfy index d3a1c98c..7bd94320 100644 --- a/src/Tools/Auditor/Auditor.dfy +++ b/src/Tools/Auditor/Auditor.dfy @@ -21,7 +21,6 @@ module {:extern "Bootstrap.Tools.Auditor"} {:options "-functionSyntax:4"} Bootst //// AST traversals //// - // TODO: can't be implemented yet because there's no representation for `assume` predicate IsAssumeStatement(e: Expr) { && e.Apply? && e.aop.Eager? From ac575fdedf455a9df04950709808aa1954d6c516 Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Wed, 31 Aug 2022 15:57:26 -0700 Subject: [PATCH 063/105] ast: Add `Unsupported` type alternative --- src/AST/Syntax.dfy | 3 +++ src/AST/Translator.Expressions.dfy | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/AST/Syntax.dfy b/src/AST/Syntax.dfy index 115413b6..99a6f3bf 100644 --- a/src/AST/Syntax.dfy +++ b/src/AST/Syntax.dfy @@ -31,6 +31,7 @@ module Types { | Collection(finite: bool, kind: CollectionKind, eltType: Type) | Function(args: seq, ret: Type) // TODO | Class(classType: ClassType) + | Unsupported(description: string) { // TODO: remove? predicate method NoLeftFunction() @@ -53,6 +54,7 @@ module Types { } case Function(args: seq, ret: Type) => false case Class(classType: ClassType) => false + case Unsupported(_) => false } } @@ -85,6 +87,7 @@ module Types { && ret.WellFormed() case Class(classType: ClassType) => && (forall i | 0 <= i < |classType.typeArgs| :: classType.typeArgs[i].WellFormed()) + case Unsupported(_) => true } } } diff --git a/src/AST/Translator.Expressions.dfy b/src/AST/Translator.Expressions.dfy index 20785e84..77089627 100644 --- a/src/AST/Translator.Expressions.dfy +++ b/src/AST/Translator.Expressions.dfy @@ -72,7 +72,7 @@ module Bootstrap.AST.Translator.Expressions { var eltTy :- TranslateType(ty.Arg); Success(DT.Collection(true, DT.CollectionKind.Seq, eltTy)) else - Failure(UnsupportedType(ty)) + Success(DT.Unsupported("Unsupported type")) } const GhostUnaryOps: set := From 3360e9457503edc30110d00ee2f245d000e025d2 Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Wed, 31 Aug 2022 15:57:46 -0700 Subject: [PATCH 064/105] ast: Allow unsupported literals --- src/AST/Translator.Expressions.dfy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AST/Translator.Expressions.dfy b/src/AST/Translator.Expressions.dfy index 77089627..e74740c6 100644 --- a/src/AST/Translator.Expressions.dfy +++ b/src/AST/Translator.Expressions.dfy @@ -224,7 +224,7 @@ module Bootstrap.AST.Translator.Expressions { else Failure(Invalid("LiteralExpr with .Value of type string must be a char or a string.")) else - Failure(UnsupportedExpr(l)) + Success(DE.Unsupported("Unsupported literal")) } function method TranslateApplyExpr(ae: C.ApplyExpr) From a35a1cb6ce50cfae9d0c664e631c8431b036f101 Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Wed, 31 Aug 2022 15:58:52 -0700 Subject: [PATCH 065/105] ast: Begin translating type synonyms, parameters --- src/AST/Entities.dfy | 3 +++ src/AST/Translator.Entity.dfy | 18 ++++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/AST/Entities.dfy b/src/AST/Entities.dfy index 25730b58..32562f94 100644 --- a/src/AST/Entities.dfy +++ b/src/AST/Entities.dfy @@ -31,6 +31,8 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities datatype SubsetType = SubsetType(boundVar: string, pred: Expr, witnessExpr: Option) + datatype TypeParameter = + TypeParameter() datatype TypeAlias = TypeAlias(base: ST.Type) datatype AbstractType = @@ -45,6 +47,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities NewType() datatype Type = + | TypeParameter(tp: TypeParameter) | SubsetType(st: SubsetType) | TypeAlias(ta: TypeAlias) | AbstractType(at: AbstractType) diff --git a/src/AST/Translator.Entity.dfy b/src/AST/Translator.Entity.dfy index cf0b8f9e..850c5e96 100644 --- a/src/AST/Translator.Entity.dfy +++ b/src/AST/Translator.Entity.dfy @@ -114,11 +114,21 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { Failure(Invalid("Unsupported member declaration type: " + TypeConv.AsString(md.FullDafnyName))) } - function TranslateTypeSynonymDecl(ts: C.TypeSynonymDecl): (e: TranslationResult) + function TranslateTypeSynonymDecl(ts: C.TypeSynonymDecl): (e: TranslationResult>) reads * { + // TODO: handle subset, nonnull types var ty :- Expr.TranslateType(ts.Rhs); - Success(E.Type.TypeAlias(E.TypeAlias.TypeAlias(ty))) + var ei :- TranslateTopLevelEntityInfo(ts); + Success([E.Entity.Type(ei, E.Type.TypeAlias(E.TypeAlias.TypeAlias(ty)))]) + } + + function TranslateTypeParameter(ts: C.TypeParameter): (e: TranslationResult>) + reads * + { + // TODO: handle variance, etc + var ei :- TranslateTopLevelEntityInfo(ts); + Success([E.Entity.Type(ei, E.Type.TypeParameter(E.TypeParameter.TypeParameter()))]) } function TranslateOpaqueTypeDecl(ot: C.OpaqueTypeDecl): (e: TranslationResult) @@ -203,6 +213,10 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { { if tl is C.TopLevelDeclWithMembers then TranslateTopLevelDeclWithMembers(tl) + else if tl is C.TypeSynonymDecl then + TranslateTypeSynonymDecl(tl) + else if tl is C.TypeParameter then + TranslateTypeParameter(tl) else if tl is C.ModuleDecl then var md := tl as C.ModuleDecl; assume ASTHeight(md.Signature) < ASTHeight(tl); From d67bfe9f88748792bfb6bd94167ba0b6cf0b046f Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Fri, 2 Sep 2022 09:45:33 -0700 Subject: [PATCH 066/105] ast: Translate programs to validated registries --- src/AST/Translator.Entity.dfy | 38 ++++++++++++++++++----------------- src/Tools/Auditor/Auditor.dfy | 4 ++-- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/AST/Translator.Entity.dfy b/src/AST/Translator.Entity.dfy index 850c5e96..850bdd0e 100644 --- a/src/AST/Translator.Entity.dfy +++ b/src/AST/Translator.Entity.dfy @@ -59,7 +59,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { function TranslateMemberEntityInfo(md: C.MemberDecl): (e: TranslationResult) reads * { - var name :- TranslateName(md.FullDafnyName); + var name :- TranslateName(md.FullName); var attrs :- TranslateAttributes(md.Attributes); var loc := TranslateLocation(md.tok); Success(E.EntityInfo(name, location := loc, attrs := attrs, members := [])) @@ -111,7 +111,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { else if md is C.Method then TranslateMethod(md) else - Failure(Invalid("Unsupported member declaration type: " + TypeConv.AsString(md.FullDafnyName))) + Failure(Invalid("Unsupported member declaration type: " + TypeConv.AsString(md.FullName))) } function TranslateTypeSynonymDecl(ts: C.TypeSynonymDecl): (e: TranslationResult>) @@ -153,7 +153,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { reads * { var parentTraits :- Seq.MapResult(ListUtils.ToSeq(t.ParentTraits), (t: C.Type) reads * => - TranslateName(t.AsTraitType.FullDafnyName)); + TranslateName(t.AsTraitType.FullName)); Success(E.Type.TraitType(E.TraitType.TraitType(parentTraits))) } @@ -161,14 +161,14 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { reads * { var parentTraits :- Seq.MapResult(ListUtils.ToSeq(c.ParentTraits), (t: C.Type) reads * => - TranslateName(t.AsTraitType.FullDafnyName)); + TranslateName(t.AsTraitType.FullName)); Success(E.Type.ClassType(E.ClassType.ClassType(parentTraits))) } function TranslateTopLevelEntityInfo(tl: C.TopLevelDecl): (e: TranslationResult) reads * { - var name :- TranslateName(tl.FullDafnyName); + var name :- TranslateName(tl.FullName); var attrs :- TranslateAttributes(tl.Attributes); var loc := TranslateLocation(tl.tok); Success(E.EntityInfo(name, location := loc, attrs := attrs, members := [])) @@ -177,7 +177,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { function TranslateTopLevelEntityInfoMembers(tl: C.TopLevelDeclWithMembers): (e: TranslationResult<(seq, E.EntityInfo)>) reads * { - var name :- TranslateName(tl.FullDafnyName); + var name :- TranslateName(tl.FullName); var attrs :- TranslateAttributes(tl.Attributes); var loc := TranslateLocation(tl.tok); var memberDecls := ListUtils.ToSeq(tl.Members); @@ -202,7 +202,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { else if tl is C.ClassDecl then TranslateClassDecl(tl) else - Failure(Invalid("Unsupported top level declaration type for " + TypeConv.AsString(tl.FullDafnyName))); + Failure(Invalid("Unsupported top level declaration type for " + TypeConv.AsString(tl.FullName))); var topEntity := E.Entity.Type(ei, top); Success([topEntity] + members) } @@ -222,7 +222,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { assume ASTHeight(md.Signature) < ASTHeight(tl); TranslateModule(md.Signature) else - Failure(Invalid("Unsupported top level declaration type for " + TypeConv.AsString(tl.FullDafnyName))) + Failure(Invalid("Unsupported top level declaration type for " + TypeConv.AsString(tl.FullName))) } function TranslateModule(sig: C.ModuleSignature): (m: TranslationResult>) @@ -230,7 +230,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { decreases ASTHeight(sig), 1 { var def := sig.ModuleDef; - var name :- TranslateName(def.FullDafnyName); + var name :- TranslateName(def.FullName); var attrs :- TranslateAttributes(def.Attributes); var loc := TranslateLocation(def.tok); var includes := ListUtils.ToSeq(def.Includes); @@ -242,30 +242,33 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { TranslateTopLevelDecl(tl.1)); var topDecls' := Seq.Flatten(topDecls); var topNames := Seq.Map((d: E.Entity) => d.ei.name, topDecls'); - assume forall nm <- topNames :: nm.ChildOf(name); //:- Need(forall nm <- topNames :: nm.ChildOf(name), Invalid("Malformed name in " + name.ToString())); + assume forall nm <- topNames :: nm.ChildOf(name); var ei := E.EntityInfo(name, location := loc, attrs := attrs, members := topNames); var mod := E.Entity.Module(ei, E.Module.Module()); Success([mod] + topDecls') } - // TODO: generate a valid program with a validated registry - function TranslateProgram(p: C.Program): (exps: TranslationResult) + function TranslateProgram(p: C.Program): (exps: TranslationResult) reads * { var moduleSigs := DictUtils.DictionaryToSeq(p.ModuleSigs); var entities :- Seq.MapResult(moduleSigs, (sig: (C.ModuleDefinition, C.ModuleSignature)) reads * => TranslateModule(sig.1)); - var regMap := Seq.FoldL((m:map, e: E.Entity) => m + map[e.ei.name := e], map[], Seq.Flatten(entities)); + var flatEntities := Seq.Flatten(entities); + var names := Seq.Map((e: E.Entity) => e.ei.name, flatEntities); + var topNames := Seq.Filter(names, (n:N.Name) => n.Name? && n.parent.Anonymous?); + :- Need(forall nm <- topNames :: nm.ChildOf(N.Anonymous), Invalid("Malformed name at top level")); + var rootEI := E.EntityInfo.EntityInfo(N.Name.Anonymous, location := E.Location.EMPTY(), attrs := [], members := topNames); + var root := E.Entity.Module(rootEI, E.Module.Module()); + var regMap := Seq.FoldL((m:map, e: E.Entity) => m + map[e.ei.name := e], map[], [root] + flatEntities); var mainMethodName :- if p.MainMethod == null then Success(None) else - var methodName :- TranslateName(p.MainMethod.FullDafnyName); + var methodName :- TranslateName(p.MainMethod.FullName); Success(Some(methodName)); - var defaultModuleName :- TranslateName(p.DefaultModule.FullDafnyName); + var defaultModuleName :- TranslateName(p.DefaultModule.FullName); var reg := E.Registry_.Registry(regMap); - Success(reg) - /* match reg.Validate() { case Pass => var prog := E.Program(reg, defaultModule := defaultModuleName, mainMethod := mainMethodName); @@ -274,6 +277,5 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { var err := Seq.Flatten(Seq.Map((e: E.ValidationError) => e.ToString(), errs)); Failure(Invalid("Failed to validate registry: " + err)) } - */ } } \ No newline at end of file diff --git a/src/Tools/Auditor/Auditor.dfy b/src/Tools/Auditor/Auditor.dfy index 7bd94320..51d91ab4 100644 --- a/src/Tools/Auditor/Auditor.dfy +++ b/src/Tools/Auditor/Auditor.dfy @@ -77,8 +77,8 @@ module {:extern "Bootstrap.Tools.Auditor"} {:options "-functionSyntax:4"} Bootst { var res := E.TranslateProgram(p); match res { - case Success(reg) => - var rpt := GenerateAuditReport(reg); + case Success(p') => + var rpt := GenerateAuditReport(p'.registry); return RenderAuditReportMarkdown(rpt); case Failure(err) => return err.ToString(); From 55dc6cf7de25109cfde41390ae3d21fea7f5013b Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Fri, 2 Sep 2022 09:45:53 -0700 Subject: [PATCH 067/105] lib: Move `Interleave` to the standard library --- src/Tools/Auditor/Report.dfy | 12 +++--------- src/Utils/Library.dfy | 5 +++++ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/Tools/Auditor/Report.dfy b/src/Tools/Auditor/Report.dfy index 1b0aeacc..527ef53a 100644 --- a/src/Tools/Auditor/Report.dfy +++ b/src/Tools/Auditor/Report.dfy @@ -70,12 +70,6 @@ module AuditReport { if b then [elt] else [] } - function method Interleave(t:T, ts: seq): seq { - if |ts| == 0 then [] - else if |ts| == 1 then ts - else [ts[0], t] + Interleave(t, ts[1..]) - } - // TODO: improve these descriptions function method AssumptionDescription(ts: set): seq<(string, string)> { MaybeElt(IsCallable in ts && HasNoBody in ts && IsGhost in ts, @@ -116,10 +110,10 @@ module AuditReport { , BoolYN(!(IsGhost in a.tags)) , BoolYN(IsExplicitAssumption(a.tags)) , BoolYN(HasExternAttribute in a.tags) - , Flatten(Interleave("
", issues)) - , Flatten(Interleave("
", mitigations)) + , Flatten(Seq.Interleave("
", issues)) + , Flatten(Seq.Interleave("
", mitigations)) ]; - "| " + Flatten(Interleave(" | ", cells)) + " |" + "| " + Flatten(Seq.Interleave(" | ", cells)) + " |" } function method RenderAuditReportMarkdown(r: Report): string { diff --git a/src/Utils/Library.dfy b/src/Utils/Library.dfy index 3cd85f40..807df61c 100644 --- a/src/Utils/Library.dfy +++ b/src/Utils/Library.dfy @@ -166,6 +166,11 @@ module Utils.Lib.Seq { else tss[0] + Flatten(tss[1..]) } + function method Interleave(sep:T, ts: seq): seq { + if |ts| <= 1 then ts + else [ts[0], sep] + Interleave(sep, ts[1..]) + } + // TODO: Why not use forall directly? function method {:opaque} All(P: T ~> bool, ts: seq) : (b: bool) reads P.reads From a28e0909a2814b130d84ff8ef47b76c2617960cb Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Fri, 2 Sep 2022 14:56:20 -0700 Subject: [PATCH 068/105] src: Make all Dafny code verify with latest changes The change to `Program` means that the definition of semantics preservation is more complex and will be harder to prove to hold. So, at the moment, those portions are commented out or assumed. --- src/AST/Entities.dfy | 19 ++++++--- src/AST/Predicates.dfy | 37 +++++++++++------ src/Backends/CSharp/Compiler.dfy | 44 ++++++++++++++------ src/Debug/Entities.dfy | 2 +- src/Passes/EliminateNegatedBinops.dfy | 23 ++-------- src/Passes/Pass.dfy | 5 ++- src/Passes/SimplifyEmptyBlocks.dfy | 22 ++-------- src/REPL/Repl.dfy | 14 +++---- src/Semantics/Interp.dfy | 2 + src/Semantics/Pure.dfy | 3 ++ src/Transforms/BottomUp.dfy | 26 +++--------- src/Transforms/Shallow.dfy | 60 +++++++++++++++++++++++---- 12 files changed, 150 insertions(+), 107 deletions(-) diff --git a/src/AST/Entities.dfy b/src/AST/Entities.dfy index 32562f94..5bb41bb3 100644 --- a/src/AST/Entities.dfy +++ b/src/AST/Entities.dfy @@ -106,6 +106,10 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities { EntityInfo(name, location := Location.EMPTY(), attrs := [], members := []) } + + function ToString(): string { + name.ToString() + " -> " + Seq.Flatten(Seq.Interleave(", ", Seq.Map((n: Name) => n.ToString(), members))) + } } datatype Entity = @@ -145,11 +149,13 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities } } - ghost predicate EntityTransformer?(f: Entity -> Entity) { + ghost predicate EntityTransformer?(f: Entity --> Entity) + { forall e :: - && f(e).kind == e.kind - && f(e).ei.name == e.ei.name - && f(e).ei.members == e.ei.members + f.requires(e) ==> + && f(e).kind == e.kind + && f(e).ei.name == e.ei.name + && f(e).ei.members == e.ei.members } type EntityTransformer = f | EntityTransformer?(f) witness e => e @@ -343,7 +349,10 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities if name.Anonymous? then AddRoot(name, entity) else AddMember(name, entity) } - function Map(f: EntityTransformer): Registry requires Valid?() { + function Map(f: EntityTransformer): Registry + requires Valid?() + requires forall e | e in entities.Values :: f.requires(e) + { Registry(map name | name in entities :: f(entities[name])) } diff --git a/src/AST/Predicates.dfy b/src/AST/Predicates.dfy index 897b0f87..80b5c2b0 100644 --- a/src/AST/Predicates.dfy +++ b/src/AST/Predicates.dfy @@ -1,32 +1,47 @@ +include "Entities.dfy" include "Syntax.dfy" module Bootstrap.AST.Predicates { module Shallow { import opened Utils.Lib + import opened Entities import opened Syntax - /* - function method All_Method(m: Method, P: Expr -> bool) : bool { - match m { - case Method(CompileName_, methodBody) => P(methodBody) + function method All_OptionalExpr(oe: Datatypes.Option, P: Expr -> bool) : bool { + match oe { + case Some(e) => P(e) + case None => true } } - function method All_Program(p: Program, P: Expr -> bool) : bool { - match p { - case Program(mainMethod) => All_Method(mainMethod, P) + function method All_Callable(c: Callable, P: Expr -> bool) : bool { + && Seq.All(P, c.req) + && Seq.All(P, c.ens) + && All_OptionalExpr(c.body, P) + } + + function method All_Entity(e: Entity, P: Expr -> bool) : bool { + match e { + case Definition(_, d) => d.Callable? ==> All_Callable(d.ci, P) + // TODO: add fields once they contain expressions + case _ => true } } + function method All_Program(p: Program, P: Expr -> bool) : (r: bool) + { + forall e | e in p.registry.entities.Values :: All_Entity(e, P) + } + function method All(p: Program, P: Expr -> bool) : bool { All_Program(p, P) } - */ } module DeepImpl { abstract module Base { import opened Utils.Lib + import opened Entities import opened Syntax import Shallow @@ -48,15 +63,13 @@ abstract module Base { !All_Expr(e, e' => !P(e')) } - /* - function method All_Method(m: Method, P: Expr -> bool) : bool { - Shallow.All_Method(m, e => All_Expr(e, P)) + function method All_Entity(ent: Entity, P: Expr -> bool) : bool { + Shallow.All_Entity(ent, e => All_Expr(e, P)) } function method All_Program(p: Program, P: Expr -> bool) : bool { Shallow.All_Program(p, e => All_Expr(e, P)) } - */ // // Lemmas diff --git a/src/Backends/CSharp/Compiler.dfy b/src/Backends/CSharp/Compiler.dfy index a68e4447..945a6a40 100644 --- a/src/Backends/CSharp/Compiler.dfy +++ b/src/Backends/CSharp/Compiler.dfy @@ -1,7 +1,7 @@ include "../../Interop/CSharpDafnyASTModel.dfy" include "../../Interop/CSharpInterop.dfy" include "../../Interop/CSharpDafnyInterop.dfy" -include "../../AST/Translator.dfy" +include "../../AST/Translator.Entity.dfy" include "../../Passes/EliminateNegatedBinops.dfy" include "../../Transforms/BottomUp.dfy" include "../../Utils/Library.dfy" @@ -14,6 +14,7 @@ module {:extern "Bootstrap.Backends.CSharp"} Bootstrap.Backends.CSharp { import Utils.StrTree import AST.Predicates import AST.Translator + import AST.Translator.Entity import Transforms.BottomUp import Passes.EliminateNegatedBinops import opened AST.Predicates.Deep @@ -21,10 +22,12 @@ module {:extern "Bootstrap.Backends.CSharp"} Bootstrap.Backends.CSharp { module Compiler { import opened StrTree_ = Utils.StrTree import opened Interop.CSharpDafnyInterop + import E = AST.Entities import opened AST.Syntax import AST.Predicates import Passes.EliminateNegatedBinops import opened AST.Predicates.Deep + import opened Utils.Lib.Datatypes function method CompileType(t: Type): StrTree { match t { @@ -48,6 +51,7 @@ module Compiler { case Class(_) => Unsupported case Function(_, _) => Unsupported case Unit => Unsupported + case Unsupported(_) => Unsupported } } @@ -66,6 +70,7 @@ module Compiler { Call(Str("new BigRational"), [CompileInt(n), CompileInt(d)]) case LitChar(c: char) => SingleQuote(c) case LitString(s: string, verbatim: bool) => DoubleQuote(s) // FIXME verbatim + case LitUnit() => Unsupported } } @@ -217,6 +222,8 @@ module Compiler { case Eager(TernaryOp(op)) => Unsupported case Eager(Builtin(Display(ty))) => CompileDisplayExpr(ty, Lib.Seq.Map((e requires e in es => CompileExpr(e)), es)) + case Eager(Builtin(Predicate(_))) => + Unsupported case Eager(Builtin(Print)) => Concat("\n", Lib.Seq.Map(e requires e in es => CompilePrint(e), es)) case Eager(DataConstructor(name, typeArgs)) => Unsupported @@ -236,28 +243,36 @@ module Compiler { Str("} else {"), SepSeq(Lib.Datatypes.None, [Str(" "), cEls]), Str("}")]) + case Unsupported(_) => Unsupported } } - function method CompileMethod(m: Method) : StrTree - requires Deep.All_Method(m, EliminateNegatedBinops.NotANegatedBinopExpr) - requires Deep.All_Method(m, Exprs.WellFormed) + function method CompileEntity(ent: E.Entity) : StrTree + requires Deep.All_Entity(ent, EliminateNegatedBinops.NotANegatedBinopExpr) + requires Deep.All_Entity(ent, Exprs.WellFormed) { - match m { - case Method(nm, methodBody) => CompileExpr(methodBody) + var mbody := if ent.Definition? && ent.d.Callable? then ent.d.ci.body else None; + match mbody { + case None => StrTree.Str("") + case Some(e) => CompileExpr(e) } } - function method CompileProgram(p: Program) : StrTree + function method CompileProgram(p: E.Program) : StrTree requires Deep.All_Program(p, EliminateNegatedBinops.NotANegatedBinopExpr) requires Deep.All_Program(p, Exprs.WellFormed) { - match p { - case Program(mainMethod) => CompileMethod(mainMethod) - } + var names := p.registry.SortedNames(); + var entities := Seq.Map(p.registry.Get, names); + // TODO: prove + assume forall e | e in entities :: + Deep.All_Entity(e, EliminateNegatedBinops.NotANegatedBinopExpr) && + Deep.All_Entity(e, Exprs.WellFormed); + var strs := Seq.Map(CompileEntity, entities); + StrTree.SepSeq(Some("\n\n"), strs) } - method AlwaysCompileProgram(p: Program) returns (st: StrTree) + method AlwaysCompileProgram(p: E.Program) returns (st: StrTree) requires Deep.All_Program(p, EliminateNegatedBinops.NotANegatedBinopExpr) { // TODO: this property is tedious to propagate so isn't complete yet @@ -291,7 +306,7 @@ module Compiler { method Compile(dafnyProgram: CSharpDafnyASTModel.Program, wr: ConcreteSyntaxTree) { var st := new CSharpDafnyInterop.SyntaxTreeAdapter(wr); - match Translator.TranslateProgram(dafnyProgram) { + match Entity.TranslateProgram(dafnyProgram) { case Success(translated) => var lowered := EliminateNegatedBinops.Apply(translated); @@ -300,11 +315,14 @@ module Compiler { // but it seems cleaner to state that we need ``NotANegatedBinopExpr`` in the // preconditions, as it is more precise and ``Tr_Expr_Post`` might be expanded // in the future. + // TODO: need to redo this for all the entities in the program + /* AllChildren_Expr_weaken( lowered.mainMethod.methodBody, EliminateNegatedBinops.Tr_Expr_Post, EliminateNegatedBinops.NotANegatedBinopExpr); - assert Deep.All_Program(lowered, EliminateNegatedBinops.NotANegatedBinopExpr); + */ + assume Deep.All_Program(lowered, EliminateNegatedBinops.NotANegatedBinopExpr); var compiled := Compiler.AlwaysCompileProgram(lowered); WriteAST(st, compiled); diff --git a/src/Debug/Entities.dfy b/src/Debug/Entities.dfy index 2008a378..fe8d500b 100644 --- a/src/Debug/Entities.dfy +++ b/src/Debug/Entities.dfy @@ -52,7 +52,7 @@ module {:options "-functionSyntax:4"} Bootstrap.Debug.Entities { { for i := 0 to |names| { var name := names[i]; - registry.Decreases_TransitiveMembers(names, name); + registry.Decreases_TransitiveMembers_Many(names, name); DumpEntity(name, indent); print "\n"; } diff --git a/src/Passes/EliminateNegatedBinops.dfy b/src/Passes/EliminateNegatedBinops.dfy index f58d06cb..e334279a 100644 --- a/src/Passes/EliminateNegatedBinops.dfy +++ b/src/Passes/EliminateNegatedBinops.dfy @@ -24,6 +24,7 @@ module Bootstrap.Passes.EliminateNegatedBinops { import opened Utils.Lib.Datatypes import opened Transforms.BottomUp + import opened AST.Entities import opened AST.Syntax import opened AST.Predicates import opened Semantics.Interp @@ -250,34 +251,16 @@ module Bootstrap.Passes.EliminateNegatedBinops { Deep.All_Program(p, Tr_Expr_Post) } - function method Apply_Method(m: Method) : (m': Method) - ensures Deep.All_Method(m', Tr_Expr_Post) - ensures Tr_Expr_Rel(m.methodBody, m'.methodBody) - // Apply the transformation to a method. - // - // We need it on a temporary basis, so that we can apply the transformation - // to all the methods in a program (we haven't defined modules, classes, - // etc. yet). When the `Program` definition is complete enough, we will - // remove this definition and exclusively use `Apply`. - { - - Deep.All_Expr_True_Forall(Tr_Expr.f.requires); - assert Deep.All_Method(m, Tr_Expr.f.requires); - TrPreservesRel(); - Map_Method_PreservesRel(m, Tr_Expr, Tr_Expr_Rel); - Map_Method(m, Tr_Expr) - } - function method Apply(p: Program) : (p': Program) requires Tr_Pre(p) ensures Tr_Post(p') - ensures Tr_Expr_Rel(p.mainMethod.methodBody, p'.mainMethod.methodBody) + //ensures Tr_Expr_Rel(p.mainMethod.methodBody, p'.mainMethod.methodBody) // Apply the transformation to a program. { Deep.All_Expr_True_Forall(Tr_Expr.f.requires); assert Deep.All_Program(p, Tr_Expr.f.requires); TrPreservesRel(); - Map_Program_PreservesRel(p, Tr_Expr, Tr_Expr_Rel); + //Map_Program_PreservesRel(p, Tr_Expr, Tr_Expr_Rel); Map_Program(p, Tr_Expr) } } diff --git a/src/Passes/Pass.dfy b/src/Passes/Pass.dfy index 1a928834..196b9fd4 100644 --- a/src/Passes/Pass.dfy +++ b/src/Passes/Pass.dfy @@ -1,9 +1,11 @@ +include "../AST/Entities.dfy" include "../AST/Syntax.dfy" include "../Semantics/Equiv.dfy" module Bootstrap.Passes.Pass { // Abstract module describing a compiler pass. + import opened AST.Entities import opened AST.Syntax import opened Semantics.Equiv @@ -20,5 +22,6 @@ module Bootstrap.Passes.Pass { // the input and the output. requires Tr_Pre(p) ensures Tr_Post(p) - ensures EqInterp(p.mainMethod.methodBody, p'.mainMethod.methodBody) + // TODO! + //ensures EqInterp(p.mainMethod.methodBody, p'.mainMethod.methodBody) } diff --git a/src/Passes/SimplifyEmptyBlocks.dfy b/src/Passes/SimplifyEmptyBlocks.dfy index 8c172399..51c96c83 100644 --- a/src/Passes/SimplifyEmptyBlocks.dfy +++ b/src/Passes/SimplifyEmptyBlocks.dfy @@ -612,6 +612,7 @@ module Simplify { import opened Utils.Lib.Datatypes import opened Transforms.BottomUp + import opened AST.Entities import opened AST.Syntax import opened AST.Predicates import opened Semantics.Interp @@ -668,33 +669,16 @@ module Simplify { TR(Simplify_Single, Tr_Expr_Post)) - function method Apply_Method(m: Method) : (m': Method) - ensures Deep.All_Method(m', Tr_Expr_Post) - ensures Tr_Expr_Rel(m.methodBody, m'.methodBody) - // Apply the transformation to a method. - // - // We need it on a temporary basis, so that we can apply the transformation - // to all the methods in a program (we haven't defined modules, classes, - // etc. yet). When the `Program` definition is complete enough, we will - // remove this definition and exclusively use `Apply`. - { - Deep.All_Expr_True_Forall(Tr_Expr.f.requires); - assert Deep.All_Method(m, Tr_Expr.f.requires); - EqInterp_Lift(Tr_Expr.f); - Map_Method_PreservesRel(m, Tr_Expr, Tr_Expr_Rel); - Map_Method(m, Tr_Expr) - } - function method Apply(p: Program) : (p': Program) requires Tr_Pre(p) ensures Tr_Post(p') - ensures Tr_Expr_Rel(p.mainMethod.methodBody, p'.mainMethod.methodBody) + //ensures Tr_Expr_Rel(p.mainMethod.methodBody, p'.mainMethod.methodBody) // Apply the transformation to a program. { Deep.All_Expr_True_Forall(Tr_Expr.f.requires); assert Deep.All_Program(p, Tr_Expr.f.requires); EqInterp_Lift(Tr_Expr.f); - Map_Program_PreservesRel(p, Tr_Expr, Tr_Expr_Rel); + //Map_Program_PreservesRel(p, Tr_Expr, Tr_Expr_Rel); Map_Program(p, Tr_Expr) } } diff --git a/src/REPL/Repl.dfy b/src/REPL/Repl.dfy index 577d6873..f68d1ce2 100644 --- a/src/REPL/Repl.dfy +++ b/src/REPL/Repl.dfy @@ -1,6 +1,6 @@ include "../Semantics/Interp.dfy" include "../AST/Syntax.dfy" -include "../AST/Translator.dfy" +include "../AST/Translator.Expressions.dfy" include "../Semantics/Printer.dfy" include "../Utils/Library.dfy" include "../Interop/CSharpInterop.dfy" @@ -93,7 +93,7 @@ datatype REPLError = | StackOverflow() | FailedParse(pmsg: string) | ResolutionError(rmsg: string) - | TranslationError(te: Translator.TranslationError) + | TranslationError(te: Translator.Common.TranslationError) | InterpError(ie: Interp.InterpError) | Unsupported(e: Syntax.Expr) { @@ -186,28 +186,28 @@ class REPL { } function method AbsOfFunction(fn: C.Function) - : Result + : Result reads * { var inParams := Lib.Seq.MapFilter(CSharpInterop.ListUtils.ToSeq(fn.Formals), (f: C.Formal) reads * => if f.InParam then Some(TypeConv.AsString(f.Name)) else None); - var body :- Translator.TranslateExpression(fn.Body); + var body :- Translator.Expressions.TranslateExpression(fn.Body); Success(Syntax.Exprs.Abs(inParams, body)) } function method TranslateBody(input: REPLInterop.UserInput) - : Result + : Result reads * { if input is REPLInterop.MemberDeclInput then var ei := input as REPLInterop.MemberDeclInput; if ei.Decl is C.ConstantField then var cf := ei.Decl as C.ConstantField; - Translator.TranslateExpression(cf.Rhs) + Translator.Expressions.TranslateExpression(cf.Rhs) else if ei.Decl is C.Function then AbsOfFunction(ei.Decl as C.Function) else - Failure(Translator.UnsupportedMember(ei.Decl)) + Failure(Translator.Common.UnsupportedMember(ei.Decl)) else (input.Sealed(); Lib.ControlFlow.Unreachable()) diff --git a/src/Semantics/Interp.dfy b/src/Semantics/Interp.dfy index f1c59bf6..465ff5f3 100644 --- a/src/Semantics/Interp.dfy +++ b/src/Semantics/Interp.dfy @@ -23,6 +23,7 @@ module Bootstrap.Semantics.Interp { case TernaryOp(top) => true case Builtin(Display(_)) => true case Builtin(Print()) => false + case Builtin(Predicate(_)) => false case FunctionCall() => true case DataConstructor(name, typeArgs) => Debug.TODO(false) } @@ -39,6 +40,7 @@ module Bootstrap.Semantics.Interp { case Bind(vars, vals, body) => true case Block(stmts) => true case If(cond, thn, els) => true + case Unsupported(_) => false } } diff --git a/src/Semantics/Pure.dfy b/src/Semantics/Pure.dfy index ffea548a..bdd801b5 100644 --- a/src/Semantics/Pure.dfy +++ b/src/Semantics/Pure.dfy @@ -37,6 +37,7 @@ module Bootstrap.Semantics.Pure { case BinaryOp(_) => true case TernaryOp(_) => true case Builtin(Display(_)) => true + case Builtin(Predicate(_)) => false // TODO(AAT): support this eventually case Builtin(Print) => false // For now, we actually don't model the fact that `Print` has side effects case FunctionCall() => true // TODO(SMH): ok for now because we only have terminating, pure functions case DataConstructor(_, _) => true @@ -45,6 +46,7 @@ module Bootstrap.Semantics.Pure { case Block(_) => true case Bind(_, _, _) => true case If(_, _, _) => true + case Unsupported(_) => false } predicate method {:opaque} IsPure(e: Syntax.Expr) { @@ -71,6 +73,7 @@ module Bootstrap.Semantics.Pure { case Var(v) => case Abs(vars, body) => {} case Literal(lit) => {} + case Unsupported(_) => {} case Apply(Lazy(op), args) => InterpExpr_Lazy_IsPure_SameState(e, env, ctx); case Apply(Eager(op), args) => diff --git a/src/Transforms/BottomUp.dfy b/src/Transforms/BottomUp.dfy index e52284bf..0e67cc14 100644 --- a/src/Transforms/BottomUp.dfy +++ b/src/Transforms/BottomUp.dfy @@ -10,6 +10,7 @@ include "../Transforms/Generic.dfy" include "../Transforms/Shallow.dfy" module Bootstrap.Transforms.BottomUp { + import opened AST.Entities import opened AST.Syntax import opened Utils.Lib import opened AST.Predicates @@ -155,6 +156,7 @@ module Bootstrap.Transforms.BottomUp { var e' := Expr.If(Map_Expr(cond, tr), Map_Expr(thn, tr), Map_Expr(els, tr)); assert Exprs.ConstructorsMatch(e, e'); e' + case Unsupported(_) => e } } @@ -213,6 +215,7 @@ module Bootstrap.Transforms.BottomUp { var e' := Expr.If(Map_Expr_WithRel(cond, tr, rel), Map_Expr_WithRel(thn, tr, rel), Map_Expr_WithRel(els, tr, rel)); assert Exprs.ConstructorsMatch(e, e'); e' + case Unsupported(_) => e } } @@ -272,6 +275,7 @@ module Bootstrap.Transforms.BottomUp { match e { case Var(_) => {} case Literal(_) => {} + case Unsupported(_) => {} case Abs(vars, body) => assert rel(e, tr'.f(e)); case Apply(applyOp, args) => @@ -288,15 +292,6 @@ module Bootstrap.Transforms.BottomUp { } } - function method {:opaque} Map_Method(m: Method, tr: BottomUpTransformer) : - (m': Method) - requires Deep.All_Method(m, tr.f.requires) - ensures Deep.All_Method(m', tr.post) - // Apply a transformer to a method, in a bottom-up manner. - { - Shallow.Map_Method(m, Map_Expr_Transformer(tr)) - } - function method {:opaque} Map_Program(p: Program, tr: BottomUpTransformer) : (p': Program) requires Deep.All_Program(p, tr.f.requires) @@ -306,17 +301,7 @@ module Bootstrap.Transforms.BottomUp { Shallow.Map_Program(p, Map_Expr_Transformer(tr)) } - lemma {:opaque} Map_Method_PreservesRel(m: Method, tr: BottomUpTransformer, rel: (Expr, Expr) -> bool) - requires Deep.All_Method(m, tr.f.requires) - requires TransformerDeepPreservesRel(tr.f, rel) - ensures rel(m.methodBody, Map_Method(m, tr).methodBody) - // ``Map_Method`` preserves relations - { - reveal Map_Method(); - reveal Shallow.Map_Method(); - Map_Expr_PreservesRel(m.methodBody, tr, rel); - } - +/* lemma {:opaque} Map_Program_PreservesRel(p: Program, tr: BottomUpTransformer, rel: (Expr, Expr) -> bool) requires Deep.All_Program(p, tr.f.requires) requires TransformerDeepPreservesRel(tr.f, rel) @@ -329,6 +314,7 @@ module Bootstrap.Transforms.BottomUp { reveal Shallow.Map_Program(); Map_Method_PreservesRel(p.mainMethod, tr, rel); } + */ lemma TransformationAndRel_Lift(f: Expr --> Expr, rel: (Expr, Expr) -> bool) requires RelIsTransitive(rel) diff --git a/src/Transforms/Shallow.dfy b/src/Transforms/Shallow.dfy index 55dfd87b..ef903630 100644 --- a/src/Transforms/Shallow.dfy +++ b/src/Transforms/Shallow.dfy @@ -1,29 +1,71 @@ +include "../AST/Entities.dfy" include "../AST/Syntax.dfy" include "../AST/Predicates.dfy" include "Generic.dfy" module Bootstrap.Transforms.Shallow { import opened Utils.Lib + import opened Utils.Lib.Datatypes + import opened AST.Entities import opened AST.Syntax import opened AST.Predicates import opened Generic - function method {:opaque} Map_Method(m: Method, tr: ExprTransformer) : (m': Method) - requires Shallow.All_Method(m, tr.f.requires) - ensures Shallow.All_Method(m', tr.post) // FIXME Deep + function method Map_Option(f: S ~> T, o: Option): Option + reads f.reads + requires o.Some? ==> f.requires(o.value) { - match m { - case Method(CompileName, methodBody) => - Method (CompileName, tr.f(methodBody)) + match o { + case None => None + case Some(x) => Some(f(x)) } } + function method {:opaque} Map_Callable(c: Callable, tr: ExprTransformer) : (c': Callable) + requires Shallow.All_Callable(c, tr.f.requires) + ensures Shallow.All_Callable(c', tr.post) // FIXME Deep + { + var req' := Seq.Map(tr.f, c.req); + var ens' := Seq.Map(tr.f, c.ens); + var body' := Map_Option(tr.f, c.body); + match c { + case Constructor(_, _, _) => Constructor(req', ens', body') + case Function(_, _, _) => Function(req', ens', body') + case Method(_, _, _) => Method(req', ens', body') + } + } + + function method {:opaque} Map_Entity(e: Entity, tr: ExprTransformer) : (e': Entity) + requires Shallow.All_Entity(e, tr.f.requires) + ensures Shallow.All_Entity(e', tr.post) + ensures e'.kind == e.kind + ensures e'.ei.name == e.ei.name + ensures e'.ei.members == e.ei.members + { + match e { + case Definition(ei, d) => + if d.Callable? then + Definition(ei, Callable(Map_Callable(d.ci, tr))) + else e + // TODO: add fields once they contain expressions + case _ => e + } + } + + function method Map_EntityTransformer(tr: ExprTransformer): (Entity --> Entity) { + e requires Shallow.All_Entity(e, tr.f.requires) => Map_Entity(e, tr) + } + + // TODO: prove! + lemma Map_EntityIsEntityTransformer(tr: ExprTransformer) + ensures EntityTransformer?(Map_EntityTransformer(tr)) + function method {:opaque} Map_Program(p: Program, tr: ExprTransformer) : (p': Program) requires Shallow.All_Program(p, tr.f.requires) ensures Shallow.All_Program(p', tr.post) { - match p { - case Program(mainMethod) => Program(Map_Method(mainMethod, tr)) - } + Map_EntityIsEntityTransformer(tr); + var reg' := p.registry.Map(Map_EntityTransformer(tr)); + Program(reg', defaultModule := p.defaultModule, mainMethod := p.mainMethod) } } From e7ada511312349d597dddf06847ed846f7afa1c8 Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Fri, 2 Sep 2022 15:09:10 -0700 Subject: [PATCH 069/105] auditor: Fix compilation --- GNUmakefile | 2 ++ src/Transforms/Shallow.dfy | 3 +++ 2 files changed, 5 insertions(+) diff --git a/GNUmakefile b/GNUmakefile index 2a27665e..700b0d22 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -141,6 +141,8 @@ tests: $(cs_tests) repl: $(repl_dll) FORCE dotnet exec $< +auditor: $(auditor_dll) FORCE + typecheck: $(dafny_typecheck) $(dfy_entry_points) diff --git a/src/Transforms/Shallow.dfy b/src/Transforms/Shallow.dfy index ef903630..fa8db014 100644 --- a/src/Transforms/Shallow.dfy +++ b/src/Transforms/Shallow.dfy @@ -59,6 +59,9 @@ module Bootstrap.Transforms.Shallow { // TODO: prove! lemma Map_EntityIsEntityTransformer(tr: ExprTransformer) ensures EntityTransformer?(Map_EntityTransformer(tr)) + { + assume EntityTransformer?(Map_EntityTransformer(tr)); + } function method {:opaque} Map_Program(p: Program, tr: ExprTransformer) : (p': Program) requires Shallow.All_Program(p, tr.f.requires) From 6589c15bc5cfdb7421877282eadb45add88f3b9d Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Fri, 2 Sep 2022 16:14:21 -0700 Subject: [PATCH 070/105] ast: Translate synonym and newtype subset types --- src/AST/Entities.dfy | 5 +++-- src/AST/Translator.Entity.dfy | 21 +++++++++++++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/AST/Entities.dfy b/src/AST/Entities.dfy index 5bb41bb3..43d64485 100644 --- a/src/AST/Entities.dfy +++ b/src/AST/Entities.dfy @@ -12,6 +12,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities { import opened Names import opened Syntax.Exprs + import opened Syntax.Types import ST = Syntax.Types import opened Utils.Lib.Datatypes import Utils.Lib.SetSort @@ -29,7 +30,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities Import(localName: Atom, target: Name) datatype SubsetType = - SubsetType(boundVar: string, pred: Expr, witnessExpr: Option) + SubsetType(boundVar: string, ty: Types.Type, pred: Expr, witnessExpr: Option) datatype TypeParameter = TypeParameter() @@ -44,7 +45,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities datatype DataType = DataType() datatype NewType = - NewType() + NewType(boundVar: string, ty: Types.Type, pred: Option, witnessExpr: Option) datatype Type = | TypeParameter(tp: TypeParameter) diff --git a/src/AST/Translator.Entity.dfy b/src/AST/Translator.Entity.dfy index 850bdd0e..993cb8e2 100644 --- a/src/AST/Translator.Entity.dfy +++ b/src/AST/Translator.Entity.dfy @@ -117,12 +117,23 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { function TranslateTypeSynonymDecl(ts: C.TypeSynonymDecl): (e: TranslationResult>) reads * { - // TODO: handle subset, nonnull types var ty :- Expr.TranslateType(ts.Rhs); var ei :- TranslateTopLevelEntityInfo(ts); Success([E.Entity.Type(ei, E.Type.TypeAlias(E.TypeAlias.TypeAlias(ty)))]) } + function TranslateSubsetTypeDecl(st: C.SubsetTypeDecl): (e: TranslationResult>) + reads * + { + // TODO: handle nonnull types + var x := TypeConv.AsString(st.Var.Name); + var ty :- Expr.TranslateType(st.Rhs); + var constraint :-Expr.TranslateExpression(st.Constraint); + var wit :-Expr.TranslateOptionalExpression(st.Witness); + var ei :- TranslateTopLevelEntityInfo(st); + Success([E.Entity.Type(ei, E.Type.SubsetType(E.SubsetType.SubsetType(x, ty, constraint, wit)))]) + } + function TranslateTypeParameter(ts: C.TypeParameter): (e: TranslationResult>) reads * { @@ -140,7 +151,11 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { function TranslateNewtypeDecl(nt: C.NewtypeDecl): (e: TranslationResult) reads * { - Success(E.Type.NewType(E.NewType.NewType())) + var x := TypeConv.AsString(nt.Var.Name); + var ty :- Expr.TranslateType(nt.BaseType); + var constraint :- Expr.TranslateOptionalExpression(nt.Constraint); + var wit :-Expr.TranslateOptionalExpression(nt.Witness); + Success(E.Type.NewType(E.NewType.NewType(x, ty, constraint, wit))) } function TranslateDatatypeDecl(dt: C.DatatypeDecl): (e: TranslationResult) @@ -213,6 +228,8 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { { if tl is C.TopLevelDeclWithMembers then TranslateTopLevelDeclWithMembers(tl) + else if tl is C.SubsetTypeDecl then + TranslateSubsetTypeDecl(tl) else if tl is C.TypeSynonymDecl then TranslateTypeSynonymDecl(tl) else if tl is C.TypeParameter then From 3c8a90d280ae8f1e10d71014e2484f8ee7eba3cd Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Fri, 2 Sep 2022 16:14:51 -0700 Subject: [PATCH 071/105] auditor: Audit subset types lacking witnesses --- src/Tools/Auditor/Auditor.dfy | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Tools/Auditor/Auditor.dfy b/src/Tools/Auditor/Auditor.dfy index 51d91ab4..ac8e8b5e 100644 --- a/src/Tools/Auditor/Auditor.dfy +++ b/src/Tools/Auditor/Auditor.dfy @@ -42,7 +42,10 @@ module {:extern "Bootstrap.Tools.Auditor"} {:options "-functionSyntax:4"} Bootst function GetTags(e: Entity): set { TagIf(exists a | a in e.ei.attrs :: a.name == Extern, HasExternAttribute) + TagIf(exists a | a in e.ei.attrs :: a.name == Axiom, HasAxiomAttribute) + + TagIf(e.Type? && e.t.SubsetType?, IsSubsetType) + + TagIf(e.Type? && e.t.NewType? && e.t.nt.pred.Some?, IsSubsetType) + TagIf(e.Type? && e.t.SubsetType? && e.t.st.witnessExpr.None?, HasNoWitness) + + TagIf(e.Type? && e.t.NewType? && e.t.nt.witnessExpr.None?, HasNoWitness) + TagIf(e.Definition? && e.d.Callable? && e.d.ci.body.None?, HasNoBody) + TagIf(e.Definition? && e.d.Callable? && e.d.ci.body.Some? && ContainsAssumeStatement(e.d.ci.body.value), HasAssumeInBody) + From d94bc94adb33ddbbf62421d752d6edb48d752899 Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Wed, 7 Sep 2022 16:17:49 -0700 Subject: [PATCH 072/105] ast: Add children to Expr.Unsupported constructor These are normally ignored, but included in Depth calculations to make it possible to prove termination of code that looks at them. --- src/AST/Predicates.dfy | 4 ++-- src/AST/Syntax.dfy | 14 +++++++---- src/AST/Translator.Expressions.dfy | 38 ++++++++++++++++++++++++++---- src/Backends/CSharp/Compiler.dfy | 2 +- src/Semantics/Interp.dfy | 2 +- src/Tools/Auditor/Auditor.dfy | 20 +++++++++++++--- src/Transforms/BottomUp.dfy | 7 +++--- 7 files changed, 67 insertions(+), 20 deletions(-) diff --git a/src/AST/Predicates.dfy b/src/AST/Predicates.dfy index 80b5c2b0..cdb241cd 100644 --- a/src/AST/Predicates.dfy +++ b/src/AST/Predicates.dfy @@ -126,7 +126,7 @@ abstract module Base { && All_Expr(body, P) case If(cond, thn, els) => All_Expr(cond, P) && All_Expr(thn, P) && All_Expr(els, P) - case Unsupported(_) => true + case Unsupported(_, _) => true } } @@ -144,7 +144,7 @@ abstract module Base { && All_Expr(body, P) case If(cond, thn, els) => All_Expr(cond, P) && All_Expr(thn, P) && All_Expr(els, P) - case Unsupported(_) => true + case Unsupported(_, _) => true } } diff --git a/src/AST/Syntax.dfy b/src/AST/Syntax.dfy index 99a6f3bf..8a235abc 100644 --- a/src/AST/Syntax.dfy +++ b/src/AST/Syntax.dfy @@ -259,7 +259,7 @@ module Exprs { | Block(stmts: seq) | Bind(vars: seq, vals: seq, body: Expr) | If(cond: Expr, thn: Expr, els: Expr) // DISCUSS: Lazy op node? - | Unsupported(description: string) + | Unsupported(description: string, children: seq) { function method Depth() : nat { 1 + match this { @@ -280,8 +280,11 @@ module Exprs { ) case If(cond, thn, els) => Math.Max(cond.Depth(), Math.Max(thn.Depth(), els.Depth())) - case Unsupported(_) => - 0 + // We ignore the children of Unsupported nodes for normal + // traversals, but when we do want to look at the children + // it's useful to be able to prove termination. + case Unsupported(_, args) => + Seq.MaxF(var f := (e: Expr) requires e in args => e.Depth(); f, args, 0) } } @@ -296,7 +299,8 @@ module Exprs { case Block(exprs) => exprs case Bind(vars, vals, body) => vals + [body] case If(cond, thn, els) => [cond, thn, els] - case Unsupported(_) => [] + // We ignore the children for normal traversals + case Unsupported(_, children) => [] } } } @@ -337,7 +341,7 @@ module Exprs { e'.If? case Bind(vars, vals, body) => e'.Bind? && |vars| == |e'.vars| && |vals| == |e'.vals| - case Unsupported(description) => + case Unsupported(description, _) => e'.Unsupported? && e'.description == description } } diff --git a/src/AST/Translator.Expressions.dfy b/src/AST/Translator.Expressions.dfy index e74740c6..82b3cc28 100644 --- a/src/AST/Translator.Expressions.dfy +++ b/src/AST/Translator.Expressions.dfy @@ -206,6 +206,7 @@ module Bootstrap.AST.Translator.Expressions { function method TranslateLiteral(l: C.LiteralExpr) : (e: TranslationResult) reads * + decreases ASTHeight(l), 1 { if l.Value is Boolean then Success(DE.Literal(DE.LitBool(TypeConv.AsBool(l.Value)))) @@ -224,7 +225,7 @@ module Bootstrap.AST.Translator.Expressions { else Failure(Invalid("LiteralExpr with .Value of type string must be a char or a string.")) else - Success(DE.Unsupported("Unsupported literal")) + TranslateUnsupportedExpression(l) } function method TranslateApplyExpr(ae: C.ApplyExpr) @@ -455,7 +456,7 @@ module Bootstrap.AST.Translator.Expressions { if c is C.IdentifierExpr then TranslateIdentifierExpr(c as C.IdentifierExpr) else if c is C.ConversionExpr then - Success(DE.Unsupported("conversion expr")) + TranslateUnsupportedExpression(c) else if c is C.UnaryExpr then TranslateUnary(c as C.UnaryExpr) else if c is C.BinaryExpr then @@ -486,7 +487,18 @@ module Bootstrap.AST.Translator.Expressions { TranslateITEExpr(c as C.ITEExpr) else if c is C.ConcreteSyntaxExpression then TranslateConcreteSyntaxExpression(c as C.ConcreteSyntaxExpression) - else Success(DE.Unsupported("Unsupported expression")) + else TranslateUnsupportedExpression(c) + } + + function method TranslateUnsupportedExpression(ue: C.Expression) + : (e: TranslationResult) + reads * + decreases ASTHeight(ue), 0 + { + var children := []; // TODO: ListUtils.ToSeq(ue.SubExpressions); + var children' :- Seq.MapResult(children, e requires e in children reads * => + assume Decreases(e, ue); TranslateExpression(e)); + Success(DE.Unsupported("Unsupported expression", children')) } // TODO: adapt auto-generated AST to include some nullable fields @@ -546,7 +558,7 @@ module Bootstrap.AST.Translator.Expressions { else if p is C.ExpectStmt then Success(DE.Expect) else - Failure(UnsupportedStmt(p)); + Failure(Invalid("Unsupported predicate statement type")); var e :- TranslateExpression(p.Expr); Success(DE.Apply(DE.Eager(DE.Builtin(DE.BuiltinFunction.Predicate(predTy))), [e])) } @@ -564,9 +576,25 @@ module Bootstrap.AST.Translator.Expressions { TranslateIfStmt(s as C.IfStmt) else if s is C.PredicateStmt then TranslatePredicateStmt(s as C.PredicateStmt) - else Success(DE.Unsupported("Unsupported statement")) + else + TranslateUnsupportedStatement(s) } + function method TranslateUnsupportedStatement(us: C.Statement) + : (e: TranslationResult) + reads * + decreases ASTHeight(us), 0 + { + var subexprs := []; // TODO: ListUtils.ToSeq(ue.SubExpressions); + var substmts := []; // TODO: ListUtils.ToSeq(ue.SubStatements); + var subexprs' :- Seq.MapResult(subexprs, e requires e in subexprs reads * => + assume Decreases(e, us); TranslateExpression(e)); + var substmts' :- Seq.MapResult(substmts, s requires s in substmts reads * => + assume Decreases(s, us); TranslateStatement(s)); + Success(DE.Unsupported("Unsupported expression", subexprs' + substmts')) + } + + // TODO: adapt auto-generated AST to include some nullable fields function method TranslateOptionalStatement(s: C.Statement): TranslationResult> reads * diff --git a/src/Backends/CSharp/Compiler.dfy b/src/Backends/CSharp/Compiler.dfy index 945a6a40..f16cbc0f 100644 --- a/src/Backends/CSharp/Compiler.dfy +++ b/src/Backends/CSharp/Compiler.dfy @@ -243,7 +243,7 @@ module Compiler { Str("} else {"), SepSeq(Lib.Datatypes.None, [Str(" "), cEls]), Str("}")]) - case Unsupported(_) => Unsupported + case Unsupported(_, _) => Unsupported } } diff --git a/src/Semantics/Interp.dfy b/src/Semantics/Interp.dfy index 465ff5f3..77f16caa 100644 --- a/src/Semantics/Interp.dfy +++ b/src/Semantics/Interp.dfy @@ -40,7 +40,7 @@ module Bootstrap.Semantics.Interp { case Bind(vars, vals, body) => true case Block(stmts) => true case If(cond, thn, els) => true - case Unsupported(_) => false + case Unsupported(_, _) => false } } diff --git a/src/Tools/Auditor/Auditor.dfy b/src/Tools/Auditor/Auditor.dfy index ac8e8b5e..d73ce8aa 100644 --- a/src/Tools/Auditor/Auditor.dfy +++ b/src/Tools/Auditor/Auditor.dfy @@ -21,7 +21,17 @@ module {:extern "Bootstrap.Tools.Auditor"} {:options "-functionSyntax:4"} Bootst //// AST traversals //// - predicate IsAssumeStatement(e: Expr) { + function {:opaque} Any(P: T ~> bool, ts: seq) : (b: bool) + reads P.reads + requires forall t | t in ts :: P.requires(t) + ensures b == exists t | t in ts :: P(t) + ensures b == exists i | 0 <= i < |ts| :: P(ts[i]) + { + !Seq.All(x reads P.reads requires P.requires(x) => !P(x), ts) + } + + predicate IsAssumeStatement(e: Expr) + { && e.Apply? && e.aop.Eager? && e.aop.eOp.Builtin? @@ -29,8 +39,12 @@ module {:extern "Bootstrap.Tools.Auditor"} {:options "-functionSyntax:4"} Bootst && e.aop.eOp.builtin.predTy.Assume? } - predicate ContainsAssumeStatement(e: Expr) { - Deep.Any_Expr(e, IsAssumeStatement) + predicate ContainsAssumeStatement(e: Expr) + decreases e.Depth() + { + || Deep.Any_Expr(e, (c:Expr) => IsAssumeStatement(c)) + || (&& e.Unsupported? + && Any((c:Expr) requires c.Depth() < e.Depth() => ContainsAssumeStatement(c), e.children)) } //// Tag extraction and processing //// diff --git a/src/Transforms/BottomUp.dfy b/src/Transforms/BottomUp.dfy index 0e67cc14..22c2c84e 100644 --- a/src/Transforms/BottomUp.dfy +++ b/src/Transforms/BottomUp.dfy @@ -156,7 +156,8 @@ module Bootstrap.Transforms.BottomUp { var e' := Expr.If(Map_Expr(cond, tr), Map_Expr(thn, tr), Map_Expr(els, tr)); assert Exprs.ConstructorsMatch(e, e'); e' - case Unsupported(_) => e + // TODO: for now we don't map over subexpressions of unsupported nodes + case Unsupported(_, _) => e } } @@ -215,7 +216,7 @@ module Bootstrap.Transforms.BottomUp { var e' := Expr.If(Map_Expr_WithRel(cond, tr, rel), Map_Expr_WithRel(thn, tr, rel), Map_Expr_WithRel(els, tr, rel)); assert Exprs.ConstructorsMatch(e, e'); e' - case Unsupported(_) => e + case Unsupported(_, _) => e } } @@ -275,7 +276,7 @@ module Bootstrap.Transforms.BottomUp { match e { case Var(_) => {} case Literal(_) => {} - case Unsupported(_) => {} + case Unsupported(_, _) => {} case Abs(vars, body) => assert rel(e, tr'.f(e)); case Apply(applyOp, args) => From 0192aa334ffd8c013ba7e258343b3a3148496295 Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Thu, 8 Sep 2022 08:19:52 -0700 Subject: [PATCH 073/105] auditor: Add tag for presence of `requires` --- src/Tools/Auditor/Auditor.dfy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tools/Auditor/Auditor.dfy b/src/Tools/Auditor/Auditor.dfy index d73ce8aa..3003b51d 100644 --- a/src/Tools/Auditor/Auditor.dfy +++ b/src/Tools/Auditor/Auditor.dfy @@ -64,7 +64,7 @@ module {:extern "Bootstrap.Tools.Auditor"} {:options "-functionSyntax:4"} Bootst TagIf(e.Definition? && e.d.Callable? && e.d.ci.body.Some? && ContainsAssumeStatement(e.d.ci.body.value), HasAssumeInBody) + TagIf(e.Definition? && e.d.Callable? && |e.d.ci.ens| > 0, HasEnsuresClause) + - // TagIf(e.Definition? && e.d.Callable? && |e.d.ci.req| > 0, HasRequiresClause) + + TagIf(e.Definition? && e.d.Callable? && |e.d.ci.req| > 0, HasRequiresClause) + TagIf(e.Definition? && e.d.Callable?, IsCallable) } From 7a9640f0fca021de97656de8562112fe48712dd3 Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Thu, 8 Sep 2022 08:20:29 -0700 Subject: [PATCH 074/105] auditor: Disable warnings on missing witness It seems of limited utility, but we can re-enable it in the future if desired. --- src/Tools/Auditor/Report.dfy | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Tools/Auditor/Report.dfy b/src/Tools/Auditor/Report.dfy index 527ef53a..de15cb6b 100644 --- a/src/Tools/Auditor/Report.dfy +++ b/src/Tools/Auditor/Report.dfy @@ -49,7 +49,8 @@ module AuditReport { //// Tag categorization //// predicate method IsAssumption(ts: set) { - || (IsSubsetType in ts && HasNoWitness in ts) + // This seems to be of little value at the moment + // || (IsSubsetType in ts && HasNoWitness in ts) || HasAxiomAttribute in ts || (&& IsCallable in ts && (|| (HasEnsuresClause in ts && (HasNoBody in ts || HasExternAttribute in ts)) @@ -84,9 +85,11 @@ module AuditReport { MaybeElt(HasExternAttribute in ts && HasEnsuresClause in ts, ("Extern symbol with postcondition.", "Provide a model or a test case, or both.")) + + /* MaybeElt(IsSubsetType in ts && HasNoWitness in ts, ("Subset type has no witness and could be empty.", "Provide a witness.")) + + */ MaybeElt(HasAxiomAttribute in ts, ("Has explicit `{:axiom}` attribute.", "Attempt to provide a proof or model.")) + From 987a6ae23f85b40b285d98d49189aa0d405f9920 Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Fri, 9 Sep 2022 14:59:18 -0700 Subject: [PATCH 075/105] ast: Include Unsupported children in traversals --- src/AST/Predicates.dfy | 6 ++++-- src/AST/Syntax.dfy | 12 +++++------- src/Tools/Auditor/Auditor.dfy | 15 ++------------- src/Transforms/BottomUp.dfy | 16 +++++++++++++--- 4 files changed, 24 insertions(+), 25 deletions(-) diff --git a/src/AST/Predicates.dfy b/src/AST/Predicates.dfy index cdb241cd..53fac1ed 100644 --- a/src/AST/Predicates.dfy +++ b/src/AST/Predicates.dfy @@ -126,7 +126,8 @@ abstract module Base { && All_Expr(body, P) case If(cond, thn, els) => All_Expr(cond, P) && All_Expr(thn, P) && All_Expr(els, P) - case Unsupported(_, _) => true + case Unsupported(_, exprs) => + Seq.All((e requires e in exprs => All_Expr(e, P)), exprs) } } @@ -144,7 +145,8 @@ abstract module Base { && All_Expr(body, P) case If(cond, thn, els) => All_Expr(cond, P) && All_Expr(thn, P) && All_Expr(els, P) - case Unsupported(_, _) => true + case Unsupported(_, exprs) => + Seq.All((e requires e in exprs => All_Expr(e, P)), exprs) } } diff --git a/src/AST/Syntax.dfy b/src/AST/Syntax.dfy index 8a235abc..8e95ecf0 100644 --- a/src/AST/Syntax.dfy +++ b/src/AST/Syntax.dfy @@ -280,9 +280,6 @@ module Exprs { ) case If(cond, thn, els) => Math.Max(cond.Depth(), Math.Max(thn.Depth(), els.Depth())) - // We ignore the children of Unsupported nodes for normal - // traversals, but when we do want to look at the children - // it's useful to be able to prove termination. case Unsupported(_, args) => Seq.MaxF(var f := (e: Expr) requires e in args => e.Depth(); f, args, 0) } @@ -299,8 +296,7 @@ module Exprs { case Block(exprs) => exprs case Bind(vars, vals, body) => vals + [body] case If(cond, thn, els) => [cond, thn, els] - // We ignore the children for normal traversals - case Unsupported(_, children) => [] + case Unsupported(_, children) => children } } } @@ -341,8 +337,10 @@ module Exprs { e'.If? case Bind(vars, vals, body) => e'.Bind? && |vars| == |e'.vars| && |vals| == |e'.vals| - case Unsupported(description, _) => - e'.Unsupported? && e'.description == description + case Unsupported(description, children) => + e'.Unsupported? && + e'.description == description && + |e'.children| == |children| } } diff --git a/src/Tools/Auditor/Auditor.dfy b/src/Tools/Auditor/Auditor.dfy index 3003b51d..fcb78ff0 100644 --- a/src/Tools/Auditor/Auditor.dfy +++ b/src/Tools/Auditor/Auditor.dfy @@ -21,15 +21,6 @@ module {:extern "Bootstrap.Tools.Auditor"} {:options "-functionSyntax:4"} Bootst //// AST traversals //// - function {:opaque} Any(P: T ~> bool, ts: seq) : (b: bool) - reads P.reads - requires forall t | t in ts :: P.requires(t) - ensures b == exists t | t in ts :: P(t) - ensures b == exists i | 0 <= i < |ts| :: P(ts[i]) - { - !Seq.All(x reads P.reads requires P.requires(x) => !P(x), ts) - } - predicate IsAssumeStatement(e: Expr) { && e.Apply? @@ -42,9 +33,7 @@ module {:extern "Bootstrap.Tools.Auditor"} {:options "-functionSyntax:4"} Bootst predicate ContainsAssumeStatement(e: Expr) decreases e.Depth() { - || Deep.Any_Expr(e, (c:Expr) => IsAssumeStatement(c)) - || (&& e.Unsupported? - && Any((c:Expr) requires c.Depth() < e.Depth() => ContainsAssumeStatement(c), e.children)) + Deep.Any_Expr(e, (c:Expr) => IsAssumeStatement(c)) } //// Tag extraction and processing //// @@ -102,4 +91,4 @@ module {:extern "Bootstrap.Tools.Auditor"} {:options "-functionSyntax:4"} Bootst } } } -} \ No newline at end of file +} diff --git a/src/Transforms/BottomUp.dfy b/src/Transforms/BottomUp.dfy index 22c2c84e..d27ce255 100644 --- a/src/Transforms/BottomUp.dfy +++ b/src/Transforms/BottomUp.dfy @@ -156,8 +156,12 @@ module Bootstrap.Transforms.BottomUp { var e' := Expr.If(Map_Expr(cond, tr), Map_Expr(thn, tr), Map_Expr(els, tr)); assert Exprs.ConstructorsMatch(e, e'); e' - // TODO: for now we don't map over subexpressions of unsupported nodes - case Unsupported(_, _) => e + case Unsupported(desc, exprs) => + var exprs' := Seq.Map(e requires e in exprs => Map_Expr(e, tr), exprs); + Map_All_IsMap(e requires e in exprs => Map_Expr(e, tr), exprs); + var e' := Expr.Unsupported(desc, exprs'); + assert Exprs.ConstructorsMatch(e, e'); + e' } } @@ -216,7 +220,13 @@ module Bootstrap.Transforms.BottomUp { var e' := Expr.If(Map_Expr_WithRel(cond, tr, rel), Map_Expr_WithRel(thn, tr, rel), Map_Expr_WithRel(els, tr, rel)); assert Exprs.ConstructorsMatch(e, e'); e' - case Unsupported(_, _) => e + case Unsupported(desc, exprs) => + var exprs' := Seq.Map(e requires e in exprs => Map_Expr(e, tr), exprs); + Map_All_IsMap(e requires e in exprs => Map_Expr(e, tr), exprs); + Map_All_IsMap(e requires e in exprs => Map_Expr_WithRel(e, tr, rel), exprs); + var e' := Expr.Unsupported(desc, exprs'); + assert Exprs.ConstructorsMatch(e, e'); + e' } } From 14f949751b8feb2083bc00bfa74cdfbe91345268 Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Fri, 9 Sep 2022 15:48:47 -0700 Subject: [PATCH 076/105] build: Change `dafny` variable to `DAFNY` --- GNUmakefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index 700b0d22..58d262c6 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -47,10 +47,10 @@ DafnyPipeline = $(dafny_Source)/Dafny/DafnyPipeline DafnyAST = $(dafny_Source)/Dafny/AST/DafnyAst DafnyRuntime := $(dafny_Source)/DafnyRuntime/DafnyRuntime.cs -dafny ?= dotnet run --project $(DafnyDriver).csproj $(DAFNY_DOTNET_RUN_FLAGS) -- -dafny_codegen := $(dafny) -spillTargetCode:3 -compile:0 -noVerify -useRuntimeLib -dafny_typecheck := $(dafny) -dafnyVerify:0 -dafny_verify := $(dafny) -compile:0 -trace -verifyAllModules -showSnippets:1 -vcsCores:8 +DAFNY ?= dotnet run --project $(DafnyDriver).csproj $(DAFNY_DOTNET_RUN_FLAGS) -- +dafny_codegen := $(DAFNY) -spillTargetCode:3 -compile:0 -noVerify -useRuntimeLib +dafny_typecheck := $(DAFNY) -dafnyVerify:0 +dafny_verify := $(DAFNY) -compile:0 -trace -verifyAllModules -showSnippets:1 -vcsCores:8 # Project files # ============= From cada01ce28df3a4e1d1ebc5cc3f79ced72f2c3f1 Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Fri, 9 Sep 2022 15:49:25 -0700 Subject: [PATCH 077/105] ast: Remove unnecessary import --- src/AST/Entities.dfy | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/AST/Entities.dfy b/src/AST/Entities.dfy index 43d64485..fc3ea6db 100644 --- a/src/AST/Entities.dfy +++ b/src/AST/Entities.dfy @@ -13,7 +13,6 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities import opened Names import opened Syntax.Exprs import opened Syntax.Types - import ST = Syntax.Types import opened Utils.Lib.Datatypes import Utils.Lib.SetSort import OS = Utils.Lib.Outcome.OfSeq @@ -35,7 +34,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities datatype TypeParameter = TypeParameter() datatype TypeAlias = - TypeAlias(base: ST.Type) + TypeAlias(base: Types.Type) datatype AbstractType = AbstractType() datatype TraitType = From 3203fa5a33e1035bec8c153bf8efd41116df62b6 Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Fri, 9 Sep 2022 15:51:56 -0700 Subject: [PATCH 078/105] ast: Add more `Unsupported` constructors Adds an `Unsupported` constructor to `Entity` and `Type`. Also produce an `Unsupported` expression in one more place. --- src/AST/Entities.dfy | 4 ++++ src/AST/Translator.Entity.dfy | 6 +++--- src/AST/Translator.Expressions.dfy | 25 ++++++++++++++++--------- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/AST/Entities.dfy b/src/AST/Entities.dfy index fc3ea6db..c08131c2 100644 --- a/src/AST/Entities.dfy +++ b/src/AST/Entities.dfy @@ -55,6 +55,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities | ClassType(ct: ClassType) | DataType(dt: DataType) | NewType(nt: NewType) + | Unsupported(desc: string) datatype FieldKind = Const | Var @@ -77,6 +78,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities | EImport | EType | EDefinition + | EUnsupported datatype Location = Location(file: string, line: int, column: int) @@ -118,6 +120,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities | Import(ei: EntityInfo, i: Import) | Type(ei: EntityInfo, t: Type) | Definition(ei: EntityInfo, d: Definition) + | Unsupported(description: string) { const kind := match this @@ -126,6 +129,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities case Import(ei, i) => EImport case Type(ei, t) => EType case Definition(ei, d) => EDefinition + case Unsupported(desc) => EUnsupported } datatype AttributeName = diff --git a/src/AST/Translator.Entity.dfy b/src/AST/Translator.Entity.dfy index 993cb8e2..1e3b24c3 100644 --- a/src/AST/Translator.Entity.dfy +++ b/src/AST/Translator.Entity.dfy @@ -111,7 +111,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { else if md is C.Method then TranslateMethod(md) else - Failure(Invalid("Unsupported member declaration type: " + TypeConv.AsString(md.FullName))) + Success(E.Entity.Unsupported("Unsupported member declaration type: " + TypeConv.AsString(md.FullName))) } function TranslateTypeSynonymDecl(ts: C.TypeSynonymDecl): (e: TranslationResult>) @@ -217,7 +217,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { else if tl is C.ClassDecl then TranslateClassDecl(tl) else - Failure(Invalid("Unsupported top level declaration type for " + TypeConv.AsString(tl.FullName))); + Success(E.Type.Unsupported("Unsupported top level declaration type for " + TypeConv.AsString(tl.FullName))); var topEntity := E.Entity.Type(ei, top); Success([topEntity] + members) } @@ -239,7 +239,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { assume ASTHeight(md.Signature) < ASTHeight(tl); TranslateModule(md.Signature) else - Failure(Invalid("Unsupported top level declaration type for " + TypeConv.AsString(tl.FullName))) + Success([E.Entity.Unsupported("Unsupported top level declaration type for " + TypeConv.AsString(tl.FullName))]) } function TranslateModule(sig: C.ModuleSignature): (m: TranslationResult>) diff --git a/src/AST/Translator.Expressions.dfy b/src/AST/Translator.Expressions.dfy index 82b3cc28..2c3c6bcb 100644 --- a/src/AST/Translator.Expressions.dfy +++ b/src/AST/Translator.Expressions.dfy @@ -72,6 +72,7 @@ module Bootstrap.AST.Translator.Expressions { var eltTy :- TranslateType(ty.Arg); Success(DT.Collection(true, DT.CollectionKind.Seq, eltTy)) else + // TODO: better message, using ToString Success(DT.Unsupported("Unsupported type")) } @@ -551,16 +552,22 @@ module Bootstrap.AST.Translator.Expressions { : (e: TranslationResult) reads * { - var predTy :- if p is C.AssertStmt then - Success(DE.Assert) - else if p is C.AssumeStmt then - Success(DE.Assume) - else if p is C.ExpectStmt then - Success(DE.Expect) - else - Failure(Invalid("Unsupported predicate statement type")); + var optPredTy := + if p is C.AssertStmt then + Some(DE.Assert) + else if p is C.AssumeStmt then + Some(DE.Assume) + else if p is C.ExpectStmt then + Some(DE.Expect) + else + None; var e :- TranslateExpression(p.Expr); - Success(DE.Apply(DE.Eager(DE.Builtin(DE.BuiltinFunction.Predicate(predTy))), [e])) + match optPredTy { + case Some(predTy) => + Success(DE.Apply(DE.Eager(DE.Builtin(DE.BuiltinFunction.Predicate(predTy))), [e])) + case None => + Success(DE.Unsupported("Unsupported predicate type", [])) + } } function method TranslateStatement(s: C.Statement) From 11bac3ab7623f29966bd5f5f623ebf9a04975586 Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Fri, 9 Sep 2022 15:54:30 -0700 Subject: [PATCH 079/105] =?UTF-8?q?ast:=20Don=E2=80=99t=20translate=20type?= =?UTF-8?q?=20parameters=20for=20now?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit They’re a subtype of `TopLevelDecl` in the Dafny AST, so it seems like it’d make sense to translate them to entities, but they also don’t seem to fit our notion of entity. So let’s just leave them unsupported for now. --- src/AST/Entities.dfy | 3 --- src/AST/Translator.Entity.dfy | 10 ---------- 2 files changed, 13 deletions(-) diff --git a/src/AST/Entities.dfy b/src/AST/Entities.dfy index c08131c2..f2cc2d27 100644 --- a/src/AST/Entities.dfy +++ b/src/AST/Entities.dfy @@ -31,8 +31,6 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities datatype SubsetType = SubsetType(boundVar: string, ty: Types.Type, pred: Expr, witnessExpr: Option) - datatype TypeParameter = - TypeParameter() datatype TypeAlias = TypeAlias(base: Types.Type) datatype AbstractType = @@ -47,7 +45,6 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities NewType(boundVar: string, ty: Types.Type, pred: Option, witnessExpr: Option) datatype Type = - | TypeParameter(tp: TypeParameter) | SubsetType(st: SubsetType) | TypeAlias(ta: TypeAlias) | AbstractType(at: AbstractType) diff --git a/src/AST/Translator.Entity.dfy b/src/AST/Translator.Entity.dfy index 1e3b24c3..1a70452e 100644 --- a/src/AST/Translator.Entity.dfy +++ b/src/AST/Translator.Entity.dfy @@ -134,14 +134,6 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { Success([E.Entity.Type(ei, E.Type.SubsetType(E.SubsetType.SubsetType(x, ty, constraint, wit)))]) } - function TranslateTypeParameter(ts: C.TypeParameter): (e: TranslationResult>) - reads * - { - // TODO: handle variance, etc - var ei :- TranslateTopLevelEntityInfo(ts); - Success([E.Entity.Type(ei, E.Type.TypeParameter(E.TypeParameter.TypeParameter()))]) - } - function TranslateOpaqueTypeDecl(ot: C.OpaqueTypeDecl): (e: TranslationResult) reads * { @@ -232,8 +224,6 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { TranslateSubsetTypeDecl(tl) else if tl is C.TypeSynonymDecl then TranslateTypeSynonymDecl(tl) - else if tl is C.TypeParameter then - TranslateTypeParameter(tl) else if tl is C.ModuleDecl then var md := tl as C.ModuleDecl; assume ASTHeight(md.Signature) < ASTHeight(tl); From c51446177f29068ba477c568fe0207815f6be9f3 Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Fri, 9 Sep 2022 15:58:15 -0700 Subject: [PATCH 080/105] build: Remove obsolete post-processing of C# code --- GNUmakefile | 2 -- 1 file changed, 2 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index 58d262c6..14718c38 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -108,8 +108,6 @@ $(csharp)/Compiler.cs: $(csharp)/Compiler.dfy $(dfy_models) $(dfy_interop) $(Daf $(auditor)/Auditor.cs: $(auditor)/Auditor.dfy $(dfy_models) $(dfy_interop) $(DafnyRuntime) $(dafny_codegen) $< || true sed -i.bak -e 's/__AUTOGEN__//g' "$@" - rm "$@.bak" - sed -i.bak -e 's/.*Tuple0.*//g' "$@" rm "$@.bak" # https://stackoverflow.com/questions/4247068/ # Compile the resulting C# code From 8a0e6deba0474decb8c7d409e4fcb2142a091db4 Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Fri, 9 Sep 2022 16:05:27 -0700 Subject: [PATCH 081/105] ast: Minor syntactic cleanups --- src/AST/Entities.dfy | 11 +++++------ src/AST/Translator.Entity.dfy | 6 +++--- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/AST/Entities.dfy b/src/AST/Entities.dfy index f2cc2d27..d05395e8 100644 --- a/src/AST/Entities.dfy +++ b/src/AST/Entities.dfy @@ -152,11 +152,10 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities ghost predicate EntityTransformer?(f: Entity --> Entity) { - forall e :: - f.requires(e) ==> - && f(e).kind == e.kind - && f(e).ei.name == e.ei.name - && f(e).ei.members == e.ei.members + forall e | f.requires(e) :: + && f(e).kind == e.kind + && f(e).ei.name == e.ei.name + && f(e).ei.members == e.ei.members } type EntityTransformer = f | EntityTransformer?(f) witness e => e @@ -352,7 +351,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities function Map(f: EntityTransformer): Registry requires Valid?() - requires forall e | e in entities.Values :: f.requires(e) + requires forall e <- entities.Values :: f.requires(e) { Registry(map name | name in entities :: f(entities[name])) } diff --git a/src/AST/Translator.Entity.dfy b/src/AST/Translator.Entity.dfy index 1a70452e..ab9d0c60 100644 --- a/src/AST/Translator.Entity.dfy +++ b/src/AST/Translator.Entity.dfy @@ -128,8 +128,8 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { // TODO: handle nonnull types var x := TypeConv.AsString(st.Var.Name); var ty :- Expr.TranslateType(st.Rhs); - var constraint :-Expr.TranslateExpression(st.Constraint); - var wit :-Expr.TranslateOptionalExpression(st.Witness); + var constraint :- Expr.TranslateExpression(st.Constraint); + var wit :- Expr.TranslateOptionalExpression(st.Witness); var ei :- TranslateTopLevelEntityInfo(st); Success([E.Entity.Type(ei, E.Type.SubsetType(E.SubsetType.SubsetType(x, ty, constraint, wit)))]) } @@ -146,7 +146,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { var x := TypeConv.AsString(nt.Var.Name); var ty :- Expr.TranslateType(nt.BaseType); var constraint :- Expr.TranslateOptionalExpression(nt.Constraint); - var wit :-Expr.TranslateOptionalExpression(nt.Witness); + var wit :- Expr.TranslateOptionalExpression(nt.Witness); Success(E.Type.NewType(E.NewType.NewType(x, ty, constraint, wit))) } From 4ee476711fedcc827e7fe1b8b4e2414548c9467b Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Fri, 9 Sep 2022 18:03:55 -0700 Subject: [PATCH 082/105] ast: Add `EntityInfo` to `Entity.Unsupported` --- src/AST/Entities.dfy | 4 ++-- src/AST/Translator.Entity.dfy | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/AST/Entities.dfy b/src/AST/Entities.dfy index d05395e8..56376807 100644 --- a/src/AST/Entities.dfy +++ b/src/AST/Entities.dfy @@ -117,7 +117,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities | Import(ei: EntityInfo, i: Import) | Type(ei: EntityInfo, t: Type) | Definition(ei: EntityInfo, d: Definition) - | Unsupported(description: string) + | Unsupported(ei: EntityInfo, description: string) { const kind := match this @@ -126,7 +126,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities case Import(ei, i) => EImport case Type(ei, t) => EType case Definition(ei, d) => EDefinition - case Unsupported(desc) => EUnsupported + case Unsupported(ei, desc) => EUnsupported } datatype AttributeName = diff --git a/src/AST/Translator.Entity.dfy b/src/AST/Translator.Entity.dfy index ab9d0c60..f0618a84 100644 --- a/src/AST/Translator.Entity.dfy +++ b/src/AST/Translator.Entity.dfy @@ -111,7 +111,8 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { else if md is C.Method then TranslateMethod(md) else - Success(E.Entity.Unsupported("Unsupported member declaration type: " + TypeConv.AsString(md.FullName))) + var ei :- TranslateMemberEntityInfo(md); + Success(E.Entity.Unsupported(ei, "Unsupported member declaration type: " + TypeConv.AsString(md.FullName))) } function TranslateTypeSynonymDecl(ts: C.TypeSynonymDecl): (e: TranslationResult>) @@ -229,7 +230,8 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { assume ASTHeight(md.Signature) < ASTHeight(tl); TranslateModule(md.Signature) else - Success([E.Entity.Unsupported("Unsupported top level declaration type for " + TypeConv.AsString(tl.FullName))]) + var ei :- TranslateTopLevelEntityInfo(tl); + Success([E.Entity.Unsupported(ei, "Unsupported top level declaration type for " + TypeConv.AsString(tl.FullName))]) } function TranslateModule(sig: C.ModuleSignature): (m: TranslationResult>) From 936118ce8c8fdc9bfb394172cc5979667da32468 Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Fri, 9 Sep 2022 18:10:51 -0700 Subject: [PATCH 083/105] interp: Fix `Unsupported` expressions --- src/Semantics/Pure.dfy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Semantics/Pure.dfy b/src/Semantics/Pure.dfy index bdd801b5..7ef35cb6 100644 --- a/src/Semantics/Pure.dfy +++ b/src/Semantics/Pure.dfy @@ -46,7 +46,7 @@ module Bootstrap.Semantics.Pure { case Block(_) => true case Bind(_, _, _) => true case If(_, _, _) => true - case Unsupported(_) => false + case Unsupported(_, _) => false } predicate method {:opaque} IsPure(e: Syntax.Expr) { @@ -73,7 +73,7 @@ module Bootstrap.Semantics.Pure { case Var(v) => case Abs(vars, body) => {} case Literal(lit) => {} - case Unsupported(_) => {} + case Unsupported(_, _) => {} case Apply(Lazy(op), args) => InterpExpr_Lazy_IsPure_SameState(e, env, ctx); case Apply(Eager(op), args) => From bbbb801fa02c4fcd570e665d98ca03a58751d611 Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Fri, 9 Sep 2022 18:29:32 -0700 Subject: [PATCH 084/105] lib: Add `Option.All` --- src/AST/Predicates.dfy | 9 +-------- src/Utils/Library.dfy | 8 ++++++++ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/AST/Predicates.dfy b/src/AST/Predicates.dfy index 53fac1ed..f1265b32 100644 --- a/src/AST/Predicates.dfy +++ b/src/AST/Predicates.dfy @@ -7,17 +7,10 @@ module Shallow { import opened Entities import opened Syntax - function method All_OptionalExpr(oe: Datatypes.Option, P: Expr -> bool) : bool { - match oe { - case Some(e) => P(e) - case None => true - } - } - function method All_Callable(c: Callable, P: Expr -> bool) : bool { && Seq.All(P, c.req) && Seq.All(P, c.ens) - && All_OptionalExpr(c.body, P) + && c.body.All(P) } function method All_Entity(e: Entity, P: Expr -> bool) : bool { diff --git a/src/Utils/Library.dfy b/src/Utils/Library.dfy index 807df61c..eb876807 100644 --- a/src/Utils/Library.dfy +++ b/src/Utils/Library.dfy @@ -31,6 +31,14 @@ module Utils.Lib.Datatypes { case Some(v) => Success(v) case None() => Failure(f) } + + function method All(P: T -> bool) : bool { + match this { + case Some(v) => P(v) + case None => true + } + } + } datatype Result<+T, +R> = | Success(value: T) | Failure(error: R) { From 78126ab1e132e07628eccad770542affc449b553 Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Fri, 9 Sep 2022 18:32:26 -0700 Subject: [PATCH 085/105] ; auditor: Syntactic cleanups --- src/Tools/Auditor/Auditor.dfy | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Tools/Auditor/Auditor.dfy b/src/Tools/Auditor/Auditor.dfy index fcb78ff0..59d9d3f0 100644 --- a/src/Tools/Auditor/Auditor.dfy +++ b/src/Tools/Auditor/Auditor.dfy @@ -19,15 +19,12 @@ module {:extern "Bootstrap.Tools.Auditor"} {:options "-functionSyntax:4"} Bootst import opened Utils.Lib.Datatypes import opened Utils.Lib.Seq - //// AST traversals //// + /// ## AST traversals predicate IsAssumeStatement(e: Expr) { && e.Apply? - && e.aop.Eager? - && e.aop.eOp.Builtin? - && e.aop.eOp.builtin.Predicate? - && e.aop.eOp.builtin.predTy.Assume? + && e.aop == Eager(Builtin(Predicate(Assume))) } predicate ContainsAssumeStatement(e: Expr) @@ -36,7 +33,7 @@ module {:extern "Bootstrap.Tools.Auditor"} {:options "-functionSyntax:4"} Bootst Deep.Any_Expr(e, (c:Expr) => IsAssumeStatement(c)) } - //// Tag extraction and processing //// + /// ## Tag extraction and processing function TagIf(cond: bool, t: Tag): set { if cond then {t} else {} @@ -57,7 +54,7 @@ module {:extern "Bootstrap.Tools.Auditor"} {:options "-functionSyntax:4"} Bootst TagIf(e.Definition? && e.d.Callable?, IsCallable) } - //// Report generation //// + /// ## Report generation function AddAssumptions(e: Entity, rpt: Report): Report { var tags := GetTags(e); From b4ee536349bc8c371864be7d149343b4b3408b6f Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Fri, 9 Sep 2022 18:36:18 -0700 Subject: [PATCH 086/105] lib: Add `Option.Map` --- src/Transforms/Shallow.dfy | 12 +----------- src/Utils/Library.dfy | 9 +++++++++ 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/Transforms/Shallow.dfy b/src/Transforms/Shallow.dfy index fa8db014..e9d77885 100644 --- a/src/Transforms/Shallow.dfy +++ b/src/Transforms/Shallow.dfy @@ -11,23 +11,13 @@ module Bootstrap.Transforms.Shallow { import opened AST.Predicates import opened Generic - function method Map_Option(f: S ~> T, o: Option): Option - reads f.reads - requires o.Some? ==> f.requires(o.value) - { - match o { - case None => None - case Some(x) => Some(f(x)) - } - } - function method {:opaque} Map_Callable(c: Callable, tr: ExprTransformer) : (c': Callable) requires Shallow.All_Callable(c, tr.f.requires) ensures Shallow.All_Callable(c', tr.post) // FIXME Deep { var req' := Seq.Map(tr.f, c.req); var ens' := Seq.Map(tr.f, c.ens); - var body' := Map_Option(tr.f, c.body); + var body' := c.body.Map(tr.f); match c { case Constructor(_, _, _) => Constructor(req', ens', body') case Function(_, _, _) => Function(req', ens', body') diff --git a/src/Utils/Library.dfy b/src/Utils/Library.dfy index eb876807..c1debc29 100644 --- a/src/Utils/Library.dfy +++ b/src/Utils/Library.dfy @@ -39,6 +39,15 @@ module Utils.Lib.Datatypes { } } + function method Map(f: T ~> R): Option + reads f.reads + requires this.Some? ==> f.requires(this.value) + { + match this { + case None => None + case Some(v) => Some(f(v)) + } + } } datatype Result<+T, +R> = | Success(value: T) | Failure(error: R) { From f71a09db778da071e427726de2b61c7ee5730d56 Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Fri, 9 Sep 2022 18:40:54 -0700 Subject: [PATCH 087/105] auditor: Adjust the set of tags --- src/Tools/Auditor/Auditor.dfy | 7 ++++--- src/Tools/Auditor/Report.dfy | 29 ++++++++++++++++------------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/Tools/Auditor/Auditor.dfy b/src/Tools/Auditor/Auditor.dfy index 59d9d3f0..710a19e5 100644 --- a/src/Tools/Auditor/Auditor.dfy +++ b/src/Tools/Auditor/Auditor.dfy @@ -40,13 +40,14 @@ module {:extern "Bootstrap.Tools.Auditor"} {:options "-functionSyntax:4"} Bootst } function GetTags(e: Entity): set { + // TODO: support MayNotTerminate TagIf(exists a | a in e.ei.attrs :: a.name == Extern, HasExternAttribute) + TagIf(exists a | a in e.ei.attrs :: a.name == Axiom, HasAxiomAttribute) + TagIf(e.Type? && e.t.SubsetType?, IsSubsetType) + TagIf(e.Type? && e.t.NewType? && e.t.nt.pred.Some?, IsSubsetType) + - TagIf(e.Type? && e.t.SubsetType? && e.t.st.witnessExpr.None?, HasNoWitness) + - TagIf(e.Type? && e.t.NewType? && e.t.nt.witnessExpr.None?, HasNoWitness) + - TagIf(e.Definition? && e.d.Callable? && e.d.ci.body.None?, HasNoBody) + + TagIf(e.Type? && e.t.SubsetType? && e.t.st.witnessExpr.None?, MissingWitness) + + TagIf(e.Type? && e.t.NewType? && e.t.nt.witnessExpr.None?, MissingWitness) + + TagIf(e.Definition? && e.d.Callable? && e.d.ci.body.None?, MissingBody) + TagIf(e.Definition? && e.d.Callable? && e.d.ci.body.Some? && ContainsAssumeStatement(e.d.ci.body.value), HasAssumeInBody) + TagIf(e.Definition? && e.d.Callable? && |e.d.ci.ens| > 0, HasEnsuresClause) + diff --git a/src/Tools/Auditor/Report.dfy b/src/Tools/Auditor/Report.dfy index de15cb6b..1a5aed76 100644 --- a/src/Tools/Auditor/Report.dfy +++ b/src/Tools/Auditor/Report.dfy @@ -9,15 +9,16 @@ module AuditReport { | IsGhost | IsSubsetType | IsCallable - | HasNoBody // TODO: should all tags be positive? + | MissingBody | HasAxiomAttribute | HasExternAttribute | HasVerifyFalseAttribute | HasAssumeInBody // Maybe: (expr: Expression) TODO: :axiom? | HasRequiresClause | HasEnsuresClause - | HasNoWitness // TODO: should all tags be positive? + | MissingWitness | HasJustification + | MayNotTerminate // TODO: decreases *? { function method ToString(): string { @@ -25,15 +26,16 @@ module AuditReport { case IsGhost => "IsGhost" case IsSubsetType => "IsSubsetType" case IsCallable => "IsCallable" - case HasNoBody => "HasNoBody" + case MissingBody => "MissingBody" case HasAxiomAttribute => "HasAxiomAttribute" case HasExternAttribute => "HasExternAttribute" case HasVerifyFalseAttribute => "HasVerifyFalseAttribute" case HasAssumeInBody => "HasAssumeInBody" case HasRequiresClause => "HasRequiresClause" case HasEnsuresClause => "HasEnsuresClause" - case HasNoWitness => "HasNoWitness" + case MissingWitness => "MissingWitness" case HasJustification => "HasJustification" + case MayNotTerminate => "MayNotTerminate" } } } @@ -50,11 +52,12 @@ module AuditReport { predicate method IsAssumption(ts: set) { // This seems to be of little value at the moment - // || (IsSubsetType in ts && HasNoWitness in ts) + // || (IsSubsetType in ts && MissingWitness in ts) || HasAxiomAttribute in ts || (&& IsCallable in ts - && (|| (HasEnsuresClause in ts && (HasNoBody in ts || HasExternAttribute in ts)) + && (|| (HasEnsuresClause in ts && (MissingBody in ts || HasExternAttribute in ts)) || HasAssumeInBody in ts)) + // TODO: extern with no ensures but possibly empty type } predicate method IsExplicitAssumption(ts: set) { @@ -73,12 +76,12 @@ module AuditReport { // TODO: improve these descriptions function method AssumptionDescription(ts: set): seq<(string, string)> { - MaybeElt(IsCallable in ts && HasNoBody in ts && IsGhost in ts, + MaybeElt(IsCallable in ts && MissingBody in ts && IsGhost in ts, ("Function or lemma has no body.", - "Provide a body.")) + - MaybeElt(IsCallable in ts && HasNoBody in ts && !(IsGhost in ts), + "Provide a body or add {:axiom}.")) + + MaybeElt(IsCallable in ts && MissingBody in ts && !(IsGhost in ts), ("Callable definition has no body.", - "Provide a body.")) + + "Provide a body or add {:axiom}.")) + MaybeElt(HasExternAttribute in ts && HasRequiresClause in ts, ("Extern symbol with precondition.", "Extensively test client code.")) + @@ -86,7 +89,7 @@ module AuditReport { ("Extern symbol with postcondition.", "Provide a model or a test case, or both.")) + /* - MaybeElt(IsSubsetType in ts && HasNoWitness in ts, + MaybeElt(IsSubsetType in ts && MissingWitness in ts, ("Subset type has no witness and could be empty.", "Provide a witness.")) + */ @@ -95,7 +98,7 @@ module AuditReport { "Attempt to provide a proof or model.")) + MaybeElt(HasAssumeInBody in ts, ("Has `assume` statement in body.", - "Try to replace with `assert` and prove.")) + "Try to replace with `assert` and prove or add {:axiom}.")) } lemma AllAssumptionsDescribed(ts: set) @@ -125,4 +128,4 @@ module AuditReport { "|----|--------|-------------------|------|-----|----------|\n"; FoldL((s, a) => s + RenderAssumptionMarkdown(a) + "\n", header, r.assumptions) } -} \ No newline at end of file +} From efcd9508523cc4e16260e664fc151f180247f0d6 Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Fri, 9 Sep 2022 18:51:37 -0700 Subject: [PATCH 088/105] lib: Improve `Break` and `Split` --- src/Utils/Library.dfy | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/Utils/Library.dfy b/src/Utils/Library.dfy index c1debc29..8da8dd20 100644 --- a/src/Utils/Library.dfy +++ b/src/Utils/Library.dfy @@ -310,20 +310,28 @@ module Utils.Lib.Seq { case None => []) + MapFilter(s[1..], f) } - function method Break(f: T -> bool, s: seq): (result: (seq, seq)) + /// Break a sequence into a prefix that does not satisfy a predicate and + /// a suffix beginning with an element that does (or an empty suffix if + /// no elements satisfy the predicate). + function method Break(P: T -> bool, s: seq): (result: (seq, seq)) ensures |result.1| <= |s| - ensures forall c | c in result.0 :: !f(c) + ensures forall c <- result.0 :: !P(c) + ensures |result.1| > 0 ==> P(result.1[0]) + ensures result.0 + result.1 == s { if |s| == 0 then ([], []) - else if f(s[0]) then ([], s) + else if P(s[0]) then ([], s) else - var (h, t) := Break(f, s[1..]); + var (h, t) := Break(P, s[1..]); ([s[0]] + h, t) } + /// Split a sequence into the subsequences separated by the given delimiter. function method Split(x: T, s: seq): (result: seq>) decreases |s| - ensures forall s | s in result :: x !in s + ensures forall s <- result :: x !in s + // The following should be true, but doesn't go through automatically. + //ensures Flatten(result) == Filter(s, y => y != x) { var (h, t) := Break(y => y == x, s); assert !(x in h); From 1f3d1f7d12ca12eef835ee07d905f0f3eada2e1d Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Mon, 12 Sep 2022 10:45:34 -0700 Subject: [PATCH 089/105] ast: Refactor error handling in expression translation The Failure case now should only occur for malformed ASTs, and unsupported constructs should use Success(Unuspported(...)). --- src/AST/Translator.Common.dfy | 12 ------------ src/AST/Translator.Expressions.dfy | 25 +++++++++++++++---------- 2 files changed, 15 insertions(+), 22 deletions(-) diff --git a/src/AST/Translator.Common.dfy b/src/AST/Translator.Common.dfy index 7f092af5..ba8022a6 100644 --- a/src/AST/Translator.Common.dfy +++ b/src/AST/Translator.Common.dfy @@ -9,24 +9,12 @@ module Bootstrap.AST.Translator.Common { datatype TranslationError = | Invalid(msg: string) - | GhostExpr(expr: C.Expression) - | UnsupportedType(ty: C.Type) - | UnsupportedExpr(expr: C.Expression) - | UnsupportedStmt(stmt: C.Statement) | UnsupportedMember(decl: C.MemberDecl) { function method ToString() : string { match this case Invalid(msg) => "Invalid term: " + msg - case GhostExpr(expr) => - "Ghost expression: " + TypeConv.ObjectToString(expr) - case UnsupportedType(ty) => - "Unsupported type: " + TypeConv.ObjectToString(ty) - case UnsupportedExpr(expr) => - "Unsupported expression: " + TypeConv.ObjectToString(expr) - case UnsupportedStmt(stmt) => - "Unsupported statement: " + TypeConv.ObjectToString(stmt) case UnsupportedMember(decl) => "Unsupported declaration: " + TypeConv.ObjectToString(decl) } diff --git a/src/AST/Translator.Expressions.dfy b/src/AST/Translator.Expressions.dfy index 2c3c6bcb..e8458a28 100644 --- a/src/AST/Translator.Expressions.dfy +++ b/src/AST/Translator.Expressions.dfy @@ -179,14 +179,15 @@ module Bootstrap.AST.Translator.Expressions { decreases ASTHeight(u), 0 reads * { - :- Need(u is C.UnaryOpExpr, UnsupportedExpr(u)); + :- Need(u is C.UnaryOpExpr, Invalid("Invalid unary operator")); var u := u as C.UnaryOpExpr; var op, e := u.ResolvedOp, u.E; assume Decreases(e, u); - :- Need(op !in GhostUnaryOps, GhostExpr(u)); - :- Need(op in UnaryOpMap.Keys, UnsupportedExpr(u)); var te :- TranslateExpression(e); - Success(DE.Apply(DE.Eager(DE.UnaryOp(UnaryOpMap[op])), [te])) + if op in GhostUnaryOps || op !in UnaryOpMap.Keys then + Success(DE.Unsupported("Unsupported unary operation", [te])) + else + Success(DE.Apply(DE.Eager(DE.UnaryOp(UnaryOpMap[op])), [te])) } function method TranslateBinary(b: C.BinaryExpr) @@ -198,10 +199,12 @@ module Bootstrap.AST.Translator.Expressions { // LATER b.AccumulatesForTailRecursion assume Decreases(e0, b); assume Decreases(e1, b); - :- Need(op in BinaryOpCodeMap, UnsupportedExpr(b)); var t0 :- TranslateExpression(e0); var t1 :- TranslateExpression(e1); - Success(DE.Apply(BinaryOpCodeMap[op], [t0, t1])) + if op !in BinaryOpCodeMap then + Success(DE.Unsupported("Unsupported binary operator", [t0, t1])) + else + Success(DE.Apply(BinaryOpCodeMap[op], [t0, t1])) } function method TranslateLiteral(l: C.LiteralExpr) @@ -411,18 +414,20 @@ module Bootstrap.AST.Translator.Expressions { reads * decreases ASTHeight(le), 0 { - :- Need(le.Exact, UnsupportedExpr(le)); var lhss := ListUtils.ToSeq(le.LHSs); var bvs :- Seq.MapResult(lhss, (pat: C.CasePattern) reads * => - :- Need(pat.Var != null, UnsupportedExpr(le)); + :- Need(pat.Var != null, Invalid("Null pattern variable in let expression.")); Success(TypeConv.AsString(pat.Var.Name))); var rhss := ListUtils.ToSeq(le.RHSs); var elems :- Seq.MapResult(rhss, e requires e in rhss reads * => assume Decreases(e, le); TranslateExpression(e)); - :- Need(|bvs| == |elems|, UnsupportedExpr(le)); + :- Need(|bvs| == |elems|, Invalid("Incorrect number of bound variables in let expression.")); assume Decreases(le.Body, le); var body :- TranslateExpression(le.Body); - Success(DE.Bind(bvs, elems, body)) + if !le.Exact then + Success(DE.Unsupported("Inexact let expression", [body] + elems)) + else + Success(DE.Bind(bvs, elems, body)) } function method TranslateConcreteSyntaxExpression(ce: C.ConcreteSyntaxExpression) From 388e894fa5b0b08bd73cd8197d62de61621710f7 Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Mon, 12 Sep 2022 12:57:18 -0700 Subject: [PATCH 090/105] interop: More correct List, Dictionary treatment * Mark `List` and `Dictionary` parameters nullable when they are that. * Sort dictionary keys to ensure that `DictUtils.FoldL` is a function. * Include `reads` clauses on Dafny `{:extern}` functions for above. --- src/Interop/CSharpInterop.cs | 11 +++++++---- src/Interop/CSharpInterop.dfy | 10 ++++++++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/Interop/CSharpInterop.cs b/src/Interop/CSharpInterop.cs index 53a58e1a..ffb32d2a 100644 --- a/src/Interop/CSharpInterop.cs +++ b/src/Interop/CSharpInterop.cs @@ -1,3 +1,4 @@ +#nullable enable using System.Numerics; namespace CSharpInterop { @@ -6,7 +7,7 @@ public partial class ListUtils { public static void Append(List l, T t) => l.Add(t); - public static B FoldL(Func f, B b0, List
lA) { + public static B FoldL(Func f, B b0, List? lA) { if(lA is null) { return b0; } @@ -16,7 +17,7 @@ public static B FoldL(Func f, B b0, List lA) { return b0; } - public static B FoldR(Func f, B b0, List lA) { + public static B FoldR(Func f, B b0, List? lA) { if(lA is null) { return b0; } @@ -28,11 +29,13 @@ public static B FoldR(Func f, B b0, List lA) { } public partial class DictUtils { - public static R FoldL(Func f, R r0, Dictionary d) where K : notnull { + public static R FoldL(Func f, R r0, Dictionary? d) where K : notnull { if(d is null) { return r0; } - foreach (var k in d.Keys) { + var keys = d.Keys.ToList(); + keys.Sort(); + foreach (var k in keys) { r0 = f(r0, k, d[k]); } return r0; diff --git a/src/Interop/CSharpInterop.dfy b/src/Interop/CSharpInterop.dfy index 7e329488..90888ed3 100644 --- a/src/Interop/CSharpInterop.dfy +++ b/src/Interop/CSharpInterop.dfy @@ -8,11 +8,14 @@ module {:extern "CSharpInterop"} Bootstrap.Interop.CSharpInterop { constructor {:extern} () requires false // Prevent instantiation static function method {:extern} FoldR(f: (A, B) -> B, b0: B, l: List) : B + reads l static method {:extern} Mk() returns (l: List) static method {:extern} Append(l: List, t: T) - static function method ToSeq(l: List) : seq { + static function method ToSeq(l: List) : seq + reads l + { FoldR((t, s) => [t] + s, [], l) } @@ -29,12 +32,15 @@ module {:extern "CSharpInterop"} Bootstrap.Interop.CSharpInterop { constructor {:extern} () requires false // Prevent instantiation static function method {:extern} FoldL(f: (R, K, V) -> R, r0: R, d: Dictionary): R + reads d static function method ReduceSeq(acc: seq<(K, V)>, key: K, value: V): seq<(K, V)> { acc + [(key, value)] } - static function method DictionaryToSeq(d: Dictionary): seq<(K, V)> { + static function method DictionaryToSeq(d: Dictionary): seq<(K, V)> + reads d + { FoldL(ReduceSeq, [], d) } } From 913b0111a5b1ba3880e5410280a17691b6cd5960 Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Mon, 12 Sep 2022 13:19:11 -0700 Subject: [PATCH 091/105] ; auditor: Minor refactoring * Change assumption collection process. * Improve text of error message on failure to translate program. --- src/Tools/Auditor/Auditor.dfy | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Tools/Auditor/Auditor.dfy b/src/Tools/Auditor/Auditor.dfy index 710a19e5..c20e1a14 100644 --- a/src/Tools/Auditor/Auditor.dfy +++ b/src/Tools/Auditor/Auditor.dfy @@ -57,11 +57,12 @@ module {:extern "Bootstrap.Tools.Auditor"} {:options "-functionSyntax:4"} Bootst /// ## Report generation - function AddAssumptions(e: Entity, rpt: Report): Report { + function AddAssumptions(e: Entity, assms: seq): seq { var tags := GetTags(e); - if IsAssumption(tags) - then Report(rpt.assumptions + [Assumption(e.ei.name.ToString(), tags)]) - else rpt + if IsAssumption(tags) then + assms + [Assumption(e.ei.name.ToString(), tags)] + else + assms } function FoldEntities(f: (Entity, T) -> T, reg: Registry_, init: T): T { @@ -70,7 +71,7 @@ module {:extern "Bootstrap.Tools.Auditor"} {:options "-functionSyntax:4"} Bootst } function GenerateAuditReport(reg: Registry_): Report { - FoldEntities(AddAssumptions, reg, EmptyReport) + Report(FoldEntities(AddAssumptions, reg, [])) } class {:extern} DafnyAuditor { @@ -85,7 +86,7 @@ module {:extern "Bootstrap.Tools.Auditor"} {:options "-functionSyntax:4"} Bootst var rpt := GenerateAuditReport(p'.registry); return RenderAuditReportMarkdown(rpt); case Failure(err) => - return err.ToString(); + return "Failed to translate program:\n" + err.ToString(); } } } From 3f0c810c754fbd11d75659679e4203d29cd83a2a Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Mon, 12 Sep 2022 13:51:11 -0700 Subject: [PATCH 092/105] auditor: Add some support for `decreases *` --- src/Tools/Auditor/Report.dfy | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Tools/Auditor/Report.dfy b/src/Tools/Auditor/Report.dfy index 1a5aed76..06bc3486 100644 --- a/src/Tools/Auditor/Report.dfy +++ b/src/Tools/Auditor/Report.dfy @@ -19,7 +19,6 @@ module AuditReport { | MissingWitness | HasJustification | MayNotTerminate - // TODO: decreases *? { function method ToString(): string { match this { @@ -57,11 +56,13 @@ module AuditReport { || (&& IsCallable in ts && (|| (HasEnsuresClause in ts && (MissingBody in ts || HasExternAttribute in ts)) || HasAssumeInBody in ts)) + || MayNotTerminate in ts // TODO: extern with no ensures but possibly empty type } predicate method IsExplicitAssumption(ts: set) { - HasAxiomAttribute in ts || HasAssumeInBody in ts + || HasAxiomAttribute in ts + || HasAssumeInBody in ts } //// Report rendering //// @@ -96,6 +97,9 @@ module AuditReport { MaybeElt(HasAxiomAttribute in ts, ("Has explicit `{:axiom}` attribute.", "Attempt to provide a proof or model.")) + + MaybeElt(MayNotTerminate in ts, + ("May not terminate (uses `decreases *`).", + "Provide a valid `decreases` clause.")) + MaybeElt(HasAssumeInBody in ts, ("Has `assume` statement in body.", "Try to replace with `assert` and prove or add {:axiom}.")) From 1e16c6477fa6f85dc951776bbf17d3b9a0d529d6 Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Mon, 12 Sep 2022 15:10:54 -0700 Subject: [PATCH 093/105] ast: Properly translate module children --- src/AST/Translator.Entity.dfy | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/AST/Translator.Entity.dfy b/src/AST/Translator.Entity.dfy index f0618a84..80170ad3 100644 --- a/src/AST/Translator.Entity.dfy +++ b/src/AST/Translator.Entity.dfy @@ -250,9 +250,8 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { assume ASTHeight(tl.1) < ASTHeight(sig); TranslateTopLevelDecl(tl.1)); var topDecls' := Seq.Flatten(topDecls); - var topNames := Seq.Map((d: E.Entity) => d.ei.name, topDecls'); - //:- Need(forall nm <- topNames :: nm.ChildOf(name), Invalid("Malformed name in " + name.ToString())); - assume forall nm <- topNames :: nm.ChildOf(name); + var topAndBelowNames := Seq.Map((d: E.Entity) => d.ei.name, topDecls'); + var topNames := Seq.Filter(topAndBelowNames, (n:N.Name) => n.ChildOf(name)); var ei := E.EntityInfo(name, location := loc, attrs := attrs, members := topNames); var mod := E.Entity.Module(ei, E.Module.Module()); Success([mod] + topDecls') From 999f530d14ca878bd7eb7cc5d7a04d0225269cd1 Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Mon, 12 Sep 2022 15:11:14 -0700 Subject: [PATCH 094/105] ast: Improve registry validation error messages --- src/AST/Translator.Entity.dfy | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/AST/Translator.Entity.dfy b/src/AST/Translator.Entity.dfy index 80170ad3..35f80a19 100644 --- a/src/AST/Translator.Entity.dfy +++ b/src/AST/Translator.Entity.dfy @@ -282,8 +282,8 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Translator.Entity { var prog := E.Program(reg, defaultModule := defaultModuleName, mainMethod := mainMethodName); if prog.Valid?() then Success(prog) else Failure(Invalid("Generated invalid program")) case Fail(errs) => - var err := Seq.Flatten(Seq.Map((e: E.ValidationError) => e.ToString(), errs)); - Failure(Invalid("Failed to validate registry: " + err)) + var msg := Seq.Flatten(Seq.Interleave("\n", Seq.Map((e: E.ValidationError) => e.ToString(), errs))); + Failure(Invalid("Failed to validate registry:\n" + msg)) } } -} \ No newline at end of file +} From af643b84fd836ac140f83a296b2c42537158a0af Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Mon, 12 Sep 2022 15:36:56 -0700 Subject: [PATCH 095/105] transform: Whitespace only --- src/Transforms/Shallow.dfy | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Transforms/Shallow.dfy b/src/Transforms/Shallow.dfy index e9d77885..563ee5a6 100644 --- a/src/Transforms/Shallow.dfy +++ b/src/Transforms/Shallow.dfy @@ -49,9 +49,9 @@ module Bootstrap.Transforms.Shallow { // TODO: prove! lemma Map_EntityIsEntityTransformer(tr: ExprTransformer) ensures EntityTransformer?(Map_EntityTransformer(tr)) - { - assume EntityTransformer?(Map_EntityTransformer(tr)); - } + { + assume EntityTransformer?(Map_EntityTransformer(tr)); + } function method {:opaque} Map_Program(p: Program, tr: ExprTransformer) : (p': Program) requires Shallow.All_Program(p, tr.f.requires) From 052bfccca4b76a5d3cd312dab5eea689e9a5fb87 Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Mon, 12 Sep 2022 15:45:05 -0700 Subject: [PATCH 096/105] ast: Support traversing IEnumerables in AST nodes --- src/AST/Translator.Expressions.dfy | 6 +++--- src/Interop/CSharpInterop.cs | 12 ++++++++++++ src/Interop/CSharpInterop.dfy | 13 +++++++++++++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/AST/Translator.Expressions.dfy b/src/AST/Translator.Expressions.dfy index e8458a28..ea371ff7 100644 --- a/src/AST/Translator.Expressions.dfy +++ b/src/AST/Translator.Expressions.dfy @@ -501,7 +501,7 @@ module Bootstrap.AST.Translator.Expressions { reads * decreases ASTHeight(ue), 0 { - var children := []; // TODO: ListUtils.ToSeq(ue.SubExpressions); + var children := EnumerableUtils.ToSeq(ue.SubExpressions); var children' :- Seq.MapResult(children, e requires e in children reads * => assume Decreases(e, ue); TranslateExpression(e)); Success(DE.Unsupported("Unsupported expression", children')) @@ -597,8 +597,8 @@ module Bootstrap.AST.Translator.Expressions { reads * decreases ASTHeight(us), 0 { - var subexprs := []; // TODO: ListUtils.ToSeq(ue.SubExpressions); - var substmts := []; // TODO: ListUtils.ToSeq(ue.SubStatements); + var subexprs := EnumerableUtils.ToSeq(us.SubExpressions); + var substmts := EnumerableUtils.ToSeq(us.SubStatements); var subexprs' :- Seq.MapResult(subexprs, e requires e in subexprs reads * => assume Decreases(e, us); TranslateExpression(e)); var substmts' :- Seq.MapResult(substmts, s requires s in substmts reads * => diff --git a/src/Interop/CSharpInterop.cs b/src/Interop/CSharpInterop.cs index ffb32d2a..3efeab4c 100644 --- a/src/Interop/CSharpInterop.cs +++ b/src/Interop/CSharpInterop.cs @@ -28,6 +28,18 @@ public static B FoldR(Func f, B b0, List? lA) { } } + public partial class EnumerableUtils { + public static B FoldR(Func f, B b0, IEnumerable? eA) { + if(eA is null) { + return b0; + } + foreach (var x in eA) { + b0 = f(x, b0); + } + return b0; + } + } + public partial class DictUtils { public static R FoldL(Func f, R r0, Dictionary? d) where K : notnull { if(d is null) { diff --git a/src/Interop/CSharpInterop.dfy b/src/Interop/CSharpInterop.dfy index 90888ed3..af8bc9b1 100644 --- a/src/Interop/CSharpInterop.dfy +++ b/src/Interop/CSharpInterop.dfy @@ -28,6 +28,19 @@ module {:extern "CSharpInterop"} Bootstrap.Interop.CSharpInterop { } } + class EnumerableUtils { + constructor {:extern} () requires false // Prevent instantiation + + static function method {:extern} FoldR(f: (A, B) -> B, b0: B, l: IEnumerable) : B + reads l + + static function method ToSeq(l: IEnumerable) : seq + reads l + { + FoldR((t, s) => [t] + s, [], l) + } + } + class DictUtils { constructor {:extern} () requires false // Prevent instantiation From 4b784260e917f13411fe5d89a538456c86793b23 Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Mon, 12 Sep 2022 17:47:06 -0700 Subject: [PATCH 097/105] auditor: Support multiple output formats --- src/Tools/Auditor/Auditor.dfy | 19 +++++++- src/Tools/Auditor/DafnyAuditor.csproj | 6 +++ src/Tools/Auditor/EntryPoint.cs | 62 ++++++++++++++++++++++++-- src/Tools/Auditor/Report.dfy | 28 +++++++++++- src/Tools/Auditor/assets/template.html | 31 +++++++++++++ 5 files changed, 139 insertions(+), 7 deletions(-) create mode 100644 src/Tools/Auditor/assets/template.html diff --git a/src/Tools/Auditor/Auditor.dfy b/src/Tools/Auditor/Auditor.dfy index c20e1a14..37d622b7 100644 --- a/src/Tools/Auditor/Auditor.dfy +++ b/src/Tools/Auditor/Auditor.dfy @@ -78,16 +78,31 @@ module {:extern "Bootstrap.Tools.Auditor"} {:options "-functionSyntax:4"} Bootst constructor() { } - method Audit(p: CSharpDafnyASTModel.Program) returns (r: string) + method Audit(render: Report -> string, p: CSharpDafnyASTModel.Program) returns (r: string) { var res := E.TranslateProgram(p); match res { case Success(p') => var rpt := GenerateAuditReport(p'.registry); - return RenderAuditReportMarkdown(rpt); + return render(rpt); case Failure(err) => return "Failed to translate program:\n" + err.ToString(); } } + + method AuditHTML(p: CSharpDafnyASTModel.Program) returns (r: string) + { + r := Audit(RenderAuditReportHTML, p); + } + + method AuditMarkdown(p: CSharpDafnyASTModel.Program) returns (r: string) + { + r := Audit(RenderAuditReportMarkdown, p); + } + + method AuditText(p: CSharpDafnyASTModel.Program) returns (r: string) + { + r := Audit(RenderAuditReportText, p); + } } } diff --git a/src/Tools/Auditor/DafnyAuditor.csproj b/src/Tools/Auditor/DafnyAuditor.csproj index fa42c317..b673a685 100644 --- a/src/Tools/Auditor/DafnyAuditor.csproj +++ b/src/Tools/Auditor/DafnyAuditor.csproj @@ -16,4 +16,10 @@ + + + + template.html + + diff --git a/src/Tools/Auditor/EntryPoint.cs b/src/Tools/Auditor/EntryPoint.cs index 0ee1ff55..488816d2 100644 --- a/src/Tools/Auditor/EntryPoint.cs +++ b/src/Tools/Auditor/EntryPoint.cs @@ -3,15 +3,71 @@ namespace Microsoft.Dafny.Compilers.SelfHosting.Auditor; +public class AuditorConfiguration : Plugins.PluginConfiguration { + internal static string[] args = new string[0]; + + public override void ParseArguments(string[] args) { + AuditorConfiguration.args = args; + } + + public override Plugins.Rewriter[] GetRewriters(ErrorReporter errorReporter) { + return new Plugins.Rewriter[] { new Auditor(errorReporter) }; + } + + public override Plugins.Compiler[] GetCompilers() { + return Array.Empty(); + } +} + public class Auditor : Plugins.Rewriter { private readonly DafnyAuditor auditor = new(); public Auditor(ErrorReporter reporter) : base(reporter) { } + private enum Format { HTML, Markdown, Text } + + private string GenerateHTMLReport(Program program) { + var assembly = System.Reflection.Assembly.GetExecutingAssembly(); + var templateStream = assembly.GetManifestResourceStream("template.html"); + var templateText = new StreamReader(templateStream).ReadToEnd(); + var table = auditor.AuditHTML(program); + return templateText.Replace("{{TABLE}}", table.ToString()); + } + public override void PostResolve(Program program) { - var text = auditor.Audit(program); - // TODO: write to the console or file depending on options - Console.WriteLine(text); + string? filename = null; + Format format = Format.Markdown; + string[] args = AuditorConfiguration.args; + + if (args.Count() > 1) { + Console.WriteLine("DafnyAuditor takes at most one argument"); + return; + } else if (args.Count() == 1) { + filename = AuditorConfiguration.args[0]; + if(filename.EndsWith(".html")) { + format = Format.HTML; + } else if (filename.EndsWith(".md")) { + format = Format.Markdown; + } else if (filename.EndsWith(".txt")) { + format = Format.Text; + } else { + Console.WriteLine($"Unsupported extension on report filename: {filename}"); + Console.WriteLine("Supported extensions are: .html, .md, .txt"); + return; + } + } + + var text = format switch { + Format.HTML => GenerateHTMLReport(program), + Format.Markdown => auditor.AuditMarkdown(program).ToString(), + Format.Text => auditor.AuditText(program).ToString(), + }; + + if (filename is null) { + Console.WriteLine(text); + } else { + File.WriteAllText(filename, text); + } } } diff --git a/src/Tools/Auditor/Report.dfy b/src/Tools/Auditor/Report.dfy index 06bc3486..385a7795 100644 --- a/src/Tools/Auditor/Report.dfy +++ b/src/Tools/Auditor/Report.dfy @@ -58,6 +58,7 @@ module AuditReport { || HasAssumeInBody in ts)) || MayNotTerminate in ts // TODO: extern with no ensures but possibly empty type + // TODO: loop or forall with no body } predicate method IsExplicitAssumption(ts: set) { @@ -111,7 +112,11 @@ module AuditReport { { } - function method RenderAssumptionMarkdown(a: Assumption): string { + function method RenderRow(begin: string, sep: string, end: string, cells: seq): string { + begin + Flatten(Seq.Interleave(sep, cells)) + end + } + + function method RenderAssumption(begin: string, sep: string, end: string, a: Assumption): string { var descs := AssumptionDescription(a.tags); var issues := Map( (desc: (string, string)) => desc.0, descs); var mitigations := Map( (desc: (string, string)) => desc.1, descs); @@ -123,7 +128,11 @@ module AuditReport { , Flatten(Seq.Interleave("
", issues)) , Flatten(Seq.Interleave("
", mitigations)) ]; - "| " + Flatten(Seq.Interleave(" | ", cells)) + " |" + RenderRow(begin, sep, end, cells) + } + + function method RenderAssumptionMarkdown(a: Assumption): string { + RenderAssumption("| ", " | ", " |", a) } function method RenderAuditReportMarkdown(r: Report): string { @@ -132,4 +141,19 @@ module AuditReport { "|----|--------|-------------------|------|-----|----------|\n"; FoldL((s, a) => s + RenderAssumptionMarkdown(a) + "\n", header, r.assumptions) } + + function method RenderAssumptionHTML(a: Assumption): string { + RenderAssumption("", "", "", a) + } + + function method RenderAuditReportHTML(r: Report): string { + var header := + "NameCompiledExplicit Assumption" + + "ExternIssueMitigation\n"; + FoldL((s, a) => s + RenderAssumptionHTML(a) + "\n", header, r.assumptions) + } + + function method RenderAuditReportText(r: Report): string { + "Not yet implemented" + } } diff --git a/src/Tools/Auditor/assets/template.html b/src/Tools/Auditor/assets/template.html new file mode 100644 index 00000000..ffb0a229 --- /dev/null +++ b/src/Tools/Auditor/assets/template.html @@ -0,0 +1,31 @@ + + + + + + report + + + + + + + +{{TABLE}} +
+ + + From b677d62e0c1bae62911cc0879598a9b832925cd4 Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Tue, 13 Sep 2022 16:05:11 -0700 Subject: [PATCH 098/105] ast: Add BUG comment about ghost parameter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The compiler keeps it in, even if it’s marked ghost, leading to later compilation failures. Let’s reduce it to a smaller example and file a compiler issue about it. --- src/AST/Entities.dfy | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/AST/Entities.dfy b/src/AST/Entities.dfy index 56376807..4df065b0 100644 --- a/src/AST/Entities.dfy +++ b/src/AST/Entities.dfy @@ -528,6 +528,8 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities [root] + Seq.Flatten(Seq.Map(RecursiveTransitiveMembers'(root), Members(root))) } + // BUG: the root parameter should be ghost, but causes compile failure if it is + // TODO: create and link to GitHub issue once we can reduce it to a simpler reproduction function RecursiveTransitiveMembers'(root: Name) : (Name --> seq) requires Valid?() From 87c51ce660706f8e079f7367df9e30a60c3354b7 Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Tue, 13 Sep 2022 16:06:50 -0700 Subject: [PATCH 099/105] ast: Refactor translation of `UnaryOpExpr` Avoid using `Need` to check types. --- src/AST/Translator.Expressions.dfy | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/AST/Translator.Expressions.dfy b/src/AST/Translator.Expressions.dfy index ea371ff7..d254de3c 100644 --- a/src/AST/Translator.Expressions.dfy +++ b/src/AST/Translator.Expressions.dfy @@ -174,13 +174,11 @@ module Bootstrap.AST.Translator.Expressions { ASTHeight(u) < ASTHeight(v) } - function method TranslateUnary(u: C.UnaryExpr) + function method TranslateUnaryOp(u: C.UnaryOpExpr) : (e: TranslationResult) decreases ASTHeight(u), 0 reads * { - :- Need(u is C.UnaryOpExpr, Invalid("Invalid unary operator")); - var u := u as C.UnaryOpExpr; var op, e := u.ResolvedOp, u.E; assume Decreases(e, u); var te :- TranslateExpression(e); @@ -463,8 +461,8 @@ module Bootstrap.AST.Translator.Expressions { TranslateIdentifierExpr(c as C.IdentifierExpr) else if c is C.ConversionExpr then TranslateUnsupportedExpression(c) - else if c is C.UnaryExpr then - TranslateUnary(c as C.UnaryExpr) + else if c is C.UnaryOpExpr then + TranslateUnaryOp(c as C.UnaryOpExpr) else if c is C.BinaryExpr then TranslateBinary(c as C.BinaryExpr) else if c is C.LiteralExpr then From 7583d802b429a87c981f82dd7b6e23ee0616078f Mon Sep 17 00:00:00 2001 From: Aaron Tomb Date: Tue, 13 Sep 2022 16:10:15 -0700 Subject: [PATCH 100/105] src: Include some TODO comments These all have issues, but these comments help make plans more immediately clear when browsing the code. --- src/AST/Syntax.dfy | 2 ++ src/Passes/EliminateNegatedBinops.dfy | 2 ++ src/Passes/SimplifyEmptyBlocks.dfy | 2 ++ src/Tools/Auditor/DafnyAuditor.csproj | 1 + src/Transforms/BottomUp.dfy | 1 + 5 files changed, 8 insertions(+) diff --git a/src/AST/Syntax.dfy b/src/AST/Syntax.dfy index 8e95ecf0..e762951a 100644 --- a/src/AST/Syntax.dfy +++ b/src/AST/Syntax.dfy @@ -31,6 +31,7 @@ module Types { | Collection(finite: bool, kind: CollectionKind, eltType: Type) | Function(args: seq, ret: Type) // TODO | Class(classType: ClassType) + // TODO: change string to indicate C# class and include location | Unsupported(description: string) { // TODO: remove? @@ -259,6 +260,7 @@ module Exprs { | Block(stmts: seq) | Bind(vars: seq, vals: seq, body: Expr) | If(cond: Expr, thn: Expr, els: Expr) // DISCUSS: Lazy op node? + // TODO: change string to indicate C# class and include location | Unsupported(description: string, children: seq) { function method Depth() : nat { diff --git a/src/Passes/EliminateNegatedBinops.dfy b/src/Passes/EliminateNegatedBinops.dfy index e334279a..f981b4b9 100644 --- a/src/Passes/EliminateNegatedBinops.dfy +++ b/src/Passes/EliminateNegatedBinops.dfy @@ -254,12 +254,14 @@ module Bootstrap.Passes.EliminateNegatedBinops { function method Apply(p: Program) : (p': Program) requires Tr_Pre(p) ensures Tr_Post(p') + // TODO //ensures Tr_Expr_Rel(p.mainMethod.methodBody, p'.mainMethod.methodBody) // Apply the transformation to a program. { Deep.All_Expr_True_Forall(Tr_Expr.f.requires); assert Deep.All_Program(p, Tr_Expr.f.requires); TrPreservesRel(); + // TODO //Map_Program_PreservesRel(p, Tr_Expr, Tr_Expr_Rel); Map_Program(p, Tr_Expr) } diff --git a/src/Passes/SimplifyEmptyBlocks.dfy b/src/Passes/SimplifyEmptyBlocks.dfy index 51c96c83..a26eb882 100644 --- a/src/Passes/SimplifyEmptyBlocks.dfy +++ b/src/Passes/SimplifyEmptyBlocks.dfy @@ -672,12 +672,14 @@ module Simplify { function method Apply(p: Program) : (p': Program) requires Tr_Pre(p) ensures Tr_Post(p') + // TODO //ensures Tr_Expr_Rel(p.mainMethod.methodBody, p'.mainMethod.methodBody) // Apply the transformation to a program. { Deep.All_Expr_True_Forall(Tr_Expr.f.requires); assert Deep.All_Program(p, Tr_Expr.f.requires); EqInterp_Lift(Tr_Expr.f); + // TODO //Map_Program_PreservesRel(p, Tr_Expr, Tr_Expr_Rel); Map_Program(p, Tr_Expr) } diff --git a/src/Tools/Auditor/DafnyAuditor.csproj b/src/Tools/Auditor/DafnyAuditor.csproj index b673a685..53ee3838 100644 --- a/src/Tools/Auditor/DafnyAuditor.csproj +++ b/src/Tools/Auditor/DafnyAuditor.csproj @@ -10,6 +10,7 @@ ..\..\..\..\ + diff --git a/src/Transforms/BottomUp.dfy b/src/Transforms/BottomUp.dfy index d27ce255..915fc34e 100644 --- a/src/Transforms/BottomUp.dfy +++ b/src/Transforms/BottomUp.dfy @@ -312,6 +312,7 @@ module Bootstrap.Transforms.BottomUp { Shallow.Map_Program(p, Map_Expr_Transformer(tr)) } + // TODO: prove /* lemma {:opaque} Map_Program_PreservesRel(p: Program, tr: BottomUpTransformer, rel: (Expr, Expr) -> bool) requires Deep.All_Program(p, tr.f.requires) From b99af8cee9260968cfbc7fc48c0922fd50b4a40c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Tue, 13 Sep 2022 16:33:23 -0700 Subject: [PATCH 101/105] ; ast: Add two TODO comments in Entities.dfy --- src/AST/Entities.dfy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AST/Entities.dfy b/src/AST/Entities.dfy index 4df065b0..ee3a00f2 100644 --- a/src/AST/Entities.dfy +++ b/src/AST/Entities.dfy @@ -41,7 +41,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities ClassType(parentTypes: seq) datatype DataType = DataType() - datatype NewType = + datatype NewType = // TODO: Change this into a subset type with a flag? NewType(boundVar: string, ty: Types.Type, pred: Option, witnessExpr: Option) datatype Type = @@ -118,7 +118,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities | Type(ei: EntityInfo, t: Type) | Definition(ei: EntityInfo, d: Definition) | Unsupported(ei: EntityInfo, description: string) - { + { // TODO: Define subexpressions and use that in the implementation of the auditor const kind := match this case Module(ei, m) => EModule From b8d50913c3cb17fa29df06cd8639f1cbc4770c47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Tue, 13 Sep 2022 19:01:52 -0700 Subject: [PATCH 102/105] ; repl: Add a pointer to dafny-lang/dafny#2740 --- src/REPL/Repl.dfy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/REPL/Repl.dfy b/src/REPL/Repl.dfy index f68d1ce2..82566099 100644 --- a/src/REPL/Repl.dfy +++ b/src/REPL/Repl.dfy @@ -275,8 +275,8 @@ class REPL { } else { print "Fuel exhausted, trying again with fuel := ", fuel, "\n"; } - case Failure(_) => - return Failure(v.error); // Work around a translation bug + case Failure(_) => // BUG(https://github.com/dafny-lang/dafny/issues/2740) + return Failure(v.error); } } From bd843a1fe8ec38809df0bad8a7cfad8c4688251a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Tue, 13 Sep 2022 20:19:33 -0700 Subject: [PATCH 103/105] ; ast: Remove to DISCUSS markers --- src/Semantics/Equiv.dfy | 2 +- src/Semantics/Values.dfy | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Semantics/Equiv.dfy b/src/Semantics/Equiv.dfy index c2665a5f..92ceb278 100644 --- a/src/Semantics/Equiv.dfy +++ b/src/Semantics/Equiv.dfy @@ -183,7 +183,7 @@ module Bootstrap.Semantics.Equiv { case (Closure(ctx, vars, body), Closure(ctx', vars', body')) => EqValue_Closure(v, v') - // DISCUSS: Better way to write this? Need exhaustivity checking + // See discussion in Values.dfy for why this match is written like this. case (Unit, _) => false case (Bool(b), _) => false case (Char(c), _) => false diff --git a/src/Semantics/Values.dfy b/src/Semantics/Values.dfy index e53527f0..ac6ff951 100644 --- a/src/Semantics/Values.dfy +++ b/src/Semantics/Values.dfy @@ -45,7 +45,12 @@ module Bootstrap.Semantics.Values { case (Closure(ctx, vars, body), Function(args, ret)) => true // FIXME: Need a typing relation on terms, not just values - // DISCUSS: Better way to write this? Need exhaustivity checking + // This block of cases ensures that the checks above are exhaustive. + // Using `case _ =>` would not be robust to the addition of new values + // or types; and replacing the match on the type by predicates + // (i.e. writing `Set(b) => ty.Collection? && …`) would lead to lots of + // boilerplate for complex matches (like the ones on `Collection`) and + // not be robust to the addition of extra fields in type constructors. case (Unit, _) => false case (Bool(b), _) => false case (Char(c), _) => false From 8ef4afa020f1486c95fa79e2bb521ec48fa02140 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Wed, 14 Sep 2022 02:18:41 -0700 Subject: [PATCH 104/105] src: Use // syntax for docstrings and /// syntax for sections --- src/Passes/SimplifyEmptyBlocks.dfy | 2 +- src/Tools/Auditor/Auditor.dfy | 6 +++--- src/Tools/Auditor/Report.dfy | 6 +++--- src/Utils/Library.dfy | 10 +++++----- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Passes/SimplifyEmptyBlocks.dfy b/src/Passes/SimplifyEmptyBlocks.dfy index a26eb882..a183bd62 100644 --- a/src/Passes/SimplifyEmptyBlocks.dfy +++ b/src/Passes/SimplifyEmptyBlocks.dfy @@ -605,7 +605,7 @@ module SimplifyIfThenElse { } module Simplify { - /// The final transformation + // The final transformation import Utils.Lib import Utils.Lib.Debug diff --git a/src/Tools/Auditor/Auditor.dfy b/src/Tools/Auditor/Auditor.dfy index 37d622b7..e64bb237 100644 --- a/src/Tools/Auditor/Auditor.dfy +++ b/src/Tools/Auditor/Auditor.dfy @@ -19,7 +19,7 @@ module {:extern "Bootstrap.Tools.Auditor"} {:options "-functionSyntax:4"} Bootst import opened Utils.Lib.Datatypes import opened Utils.Lib.Seq - /// ## AST traversals +/// ## AST traversals predicate IsAssumeStatement(e: Expr) { @@ -33,7 +33,7 @@ module {:extern "Bootstrap.Tools.Auditor"} {:options "-functionSyntax:4"} Bootst Deep.Any_Expr(e, (c:Expr) => IsAssumeStatement(c)) } - /// ## Tag extraction and processing +/// ## Tag extraction and processing function TagIf(cond: bool, t: Tag): set { if cond then {t} else {} @@ -55,7 +55,7 @@ module {:extern "Bootstrap.Tools.Auditor"} {:options "-functionSyntax:4"} Bootst TagIf(e.Definition? && e.d.Callable?, IsCallable) } - /// ## Report generation +/// ## Report generation function AddAssumptions(e: Entity, assms: seq): seq { var tags := GetTags(e); diff --git a/src/Tools/Auditor/Report.dfy b/src/Tools/Auditor/Report.dfy index 385a7795..2c44ef86 100644 --- a/src/Tools/Auditor/Report.dfy +++ b/src/Tools/Auditor/Report.dfy @@ -3,7 +3,7 @@ include "../../Utils/Library.dfy" module AuditReport { import opened Utils.Lib.Seq - //// Data types for report //// +/// ## Data types for report datatype Tag = | IsGhost @@ -47,7 +47,7 @@ module AuditReport { const EmptyReport := Report([]) - //// Tag categorization //// +/// ## Tag categorization predicate method IsAssumption(ts: set) { // This seems to be of little value at the moment @@ -66,7 +66,7 @@ module AuditReport { || HasAssumeInBody in ts } - //// Report rendering //// +/// ## Report rendering function method BoolYN(b: bool): string { if b then "Y" else "N" diff --git a/src/Utils/Library.dfy b/src/Utils/Library.dfy index 8da8dd20..936faa7a 100644 --- a/src/Utils/Library.dfy +++ b/src/Utils/Library.dfy @@ -310,10 +310,10 @@ module Utils.Lib.Seq { case None => []) + MapFilter(s[1..], f) } - /// Break a sequence into a prefix that does not satisfy a predicate and - /// a suffix beginning with an element that does (or an empty suffix if - /// no elements satisfy the predicate). function method Break(P: T -> bool, s: seq): (result: (seq, seq)) + // Break a sequence into a prefix that does not satisfy a predicate and + // a suffix beginning with an element that does (or an empty suffix if + // no elements satisfy the predicate). ensures |result.1| <= |s| ensures forall c <- result.0 :: !P(c) ensures |result.1| > 0 ==> P(result.1[0]) @@ -326,12 +326,12 @@ module Utils.Lib.Seq { ([s[0]] + h, t) } - /// Split a sequence into the subsequences separated by the given delimiter. function method Split(x: T, s: seq): (result: seq>) + // Split a sequence into the subsequences separated by the given delimiter. decreases |s| ensures forall s <- result :: x !in s // The following should be true, but doesn't go through automatically. - //ensures Flatten(result) == Filter(s, y => y != x) + // ensures Flatten(result) == Filter(s, y => y != x) { var (h, t) := Break(y => y == x, s); assert !(x in h); From 28299a21101342e5384664c71103c562ad707a80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Pit-Claudel?= Date: Wed, 14 Sep 2022 02:47:55 -0700 Subject: [PATCH 105/105] ; ast: Add a link in a BUG comment --- src/AST/Entities.dfy | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/AST/Entities.dfy b/src/AST/Entities.dfy index ee3a00f2..885d805b 100644 --- a/src/AST/Entities.dfy +++ b/src/AST/Entities.dfy @@ -528,8 +528,7 @@ module {:options "-functionSyntax:4"} Bootstrap.AST.Entities [root] + Seq.Flatten(Seq.Map(RecursiveTransitiveMembers'(root), Members(root))) } - // BUG: the root parameter should be ghost, but causes compile failure if it is - // TODO: create and link to GitHub issue once we can reduce it to a simpler reproduction + // BUG(https://github.com/dafny-lang/dafny/issues/2690) function RecursiveTransitiveMembers'(root: Name) : (Name --> seq) requires Valid?()