Skip to content

Commit 04a245e

Browse files
authored
[fix] Postpone type checking of invariants, axioms, init-conditions and pure functions (#865)
* save fixes * retrigger checks * fill in proof block at the end of scope construction
1 parent 7faf780 commit 04a245e

File tree

3 files changed

+235
-186
lines changed

3 files changed

+235
-186
lines changed

Src/PCompiler/CompilerCore/TypeChecker/Analyzer.cs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4+
using Antlr4.Runtime;
45
using Antlr4.Runtime.Tree;
56
using Plang.Compiler.TypeChecker.AST;
67
using Plang.Compiler.TypeChecker.AST.Declarations;
8+
using Plang.Compiler.TypeChecker.Types;
79

810
namespace Plang.Compiler.TypeChecker
911
{
@@ -32,6 +34,50 @@ public static Scope AnalyzeCompilationUnit(ICompilerConfiguration config,
3234
FunctionValidator.CheckAllPathsReturn(handler, machineFunction);
3335
}
3436

37+
// Step 3b: for PVerifier, fill in body of Invariants, Axioms, Init conditions and Pure functions
38+
foreach (var inv in globalScope.Invariants)
39+
{
40+
var ctx = (PParser.InvariantDeclContext) inv.SourceLocation;
41+
var temporaryFunction = new Function(inv.Name, inv.SourceLocation)
42+
{
43+
Scope = globalScope
44+
};
45+
inv.Body = PopulateExpr(temporaryFunction, ctx.body, PrimitiveType.Bool, handler);
46+
}
47+
48+
foreach (var axiom in globalScope.Axioms)
49+
{
50+
var ctx = (PParser.AxiomDeclContext) axiom.SourceLocation;
51+
var temporaryFunction = new Function(axiom.Name, axiom.SourceLocation)
52+
{
53+
Scope = globalScope
54+
};
55+
axiom.Body = PopulateExpr(temporaryFunction, ctx.body, PrimitiveType.Bool, handler);
56+
}
57+
58+
foreach (var initCond in globalScope.AssumeOnStarts)
59+
{
60+
var ctx = (PParser.AssumeOnStartDeclContext)initCond.SourceLocation;
61+
var temporaryFunction = new Function(initCond.Name, initCond.SourceLocation)
62+
{
63+
Scope = globalScope
64+
};
65+
initCond.Body = PopulateExpr(temporaryFunction, ctx.body, PrimitiveType.Bool, handler);
66+
}
67+
68+
foreach (var pure in globalScope.Pures)
69+
{
70+
var temporaryFunction = new Function(pure.Name, pure.SourceLocation)
71+
{
72+
Scope = pure.Scope
73+
};
74+
var context = (PParser.PureDeclContext) pure.SourceLocation;
75+
if (context.body is not null)
76+
{
77+
pure.Body = PopulateExpr(temporaryFunction, context.body, pure.Signature.ReturnType, handler);
78+
}
79+
}
80+
3581
// Step 2b: Validate no static handlers
3682
foreach (var machine in globalScope.Machines)
3783
{
@@ -120,6 +166,17 @@ public static Scope AnalyzeCompilationUnit(ICompilerConfiguration config,
120166
return globalScope;
121167
}
122168

169+
private static IPExpr PopulateExpr(Function func, ParserRuleContext ctx, PLanguageType type, ITranslationErrorHandler handler)
170+
{
171+
var exprVisitor = new ExprVisitor(func, handler);
172+
var body = exprVisitor.Visit(ctx);
173+
if (!type.IsSameTypeAs(body.Type))
174+
{
175+
throw handler.TypeMismatch(ctx, body.Type, type);
176+
}
177+
return body;
178+
}
179+
123180
private static Propagation<T> CreatePropagation<T>(Func<Function, T> getter, Action<Function, T> setter,
124181
T value)
125182
{
@@ -180,6 +237,12 @@ private static Scope BuildGlobalScope(ICompilerConfiguration config, PParser.Pro
180237
{
181238
DeclarationVisitor.PopulateDeclarations(config.Handler, globalScope, programUnit, nodesToDeclarations);
182239
}
240+
241+
// Step 3: fill in proof blocks
242+
foreach (var proofBlock in globalScope.ProofBlocks)
243+
{
244+
ProofBlockVisitor.PopulateProofBlocks(config.Handler, globalScope, proofBlock.SourceLocation, nodesToDeclarations);
245+
}
183246

184247
return globalScope;
185248
}

Src/PCompiler/CompilerCore/TypeChecker/DeclarationVisitor.cs

Lines changed: 3 additions & 186 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public static void PopulateDeclarations(
4848
public override object VisitEventDecl(PParser.EventDeclContext context)
4949
{
5050
// EVENT name=Iden
51-
var pEvent = (Event) nodesToDeclarations.Get(context);
51+
var pEvent = (Event)nodesToDeclarations.Get(context);
5252

5353
// cardinality?
5454
var hasAssume = context.cardinality()?.ASSUME() != null;
@@ -633,63 +633,21 @@ public override object VisitPureDecl(PParser.PureDeclContext context)
633633
// (COLON type)?
634634
pure.Signature.ReturnType = ResolveType(context.type());
635635

636-
if (context.body is not null)
637-
{
638-
var exprVisitor = new ExprVisitor(temporaryFunction, Handler);
639-
var body = exprVisitor.Visit(context.body);
640-
641-
if (!pure.Signature.ReturnType.IsSameTypeAs(body.Type))
642-
{
643-
throw Handler.TypeMismatch(context.body, body.Type, pure.Signature.ReturnType);
644-
}
645-
646-
pure.Body = body;
647-
}
648-
636+
// body will be handled in a later stage
649637
return pure;
650638
}
651639

652640
public override object VisitInvariantDecl(PParser.InvariantDeclContext context)
653641
{
654642
// INVARIANT name=Iden body=Expr
655643
var inv = (Invariant) nodesToDeclarations.Get(context);
656-
657-
var temporaryFunction = new Function(inv.Name, context);
658-
temporaryFunction.Scope = CurrentScope.MakeChildScope();
659-
660-
var exprVisitor = new ExprVisitor(temporaryFunction, Handler);
661-
662-
var body = exprVisitor.Visit(context.body);
663-
664-
if (!PrimitiveType.Bool.IsSameTypeAs(body.Type))
665-
{
666-
throw Handler.TypeMismatch(context.body, body.Type, PrimitiveType.Bool);
667-
}
668-
669-
inv.Body = body;
670-
671644
return inv;
672645
}
673646

674647
public override object VisitAxiomDecl(PParser.AxiomDeclContext context)
675648
{
676649
// Axiom body=Expr
677650
var inv = (Axiom) nodesToDeclarations.Get(context);
678-
679-
var temporaryFunction = new Function(inv.Name, context);
680-
temporaryFunction.Scope = CurrentScope.MakeChildScope();
681-
682-
var exprVisitor = new ExprVisitor(temporaryFunction, Handler);
683-
684-
var body = exprVisitor.Visit(context.body);
685-
686-
if (!PrimitiveType.Bool.IsSameTypeAs(body.Type))
687-
{
688-
throw Handler.TypeMismatch(context.body, body.Type, PrimitiveType.Bool);
689-
}
690-
691-
inv.Body = body;
692-
693651
return inv;
694652
}
695653

@@ -699,153 +657,12 @@ public override object VisitInvariantGroupDecl(PParser.InvariantGroupDeclContext
699657
invGroup.Invariants = context.invariantDecl().Select(Visit).Cast<Invariant>().ToList();
700658
return invGroup;
701659
}
702-
703-
private List<Invariant> ToInvariant(IPExpr e, ParserRuleContext context)
704-
{
705-
if (e is InvariantGroupRefExpr invGroupRef) return invGroupRef.Invariants;
706-
if (e is InvariantRefExpr invRef) return [invRef.Invariant];
707-
if (!PrimitiveType.Bool.IsSameTypeAs(e.Type.Canonicalize()))
708-
{
709-
throw Handler.TypeMismatch(context, e.Type, PrimitiveType.Bool);
710-
}
711-
Invariant inv = new Invariant($"tmp_inv_{Guid.NewGuid()}", e, context);
712-
return [inv];
713-
}
714-
715-
public override object VisitProofBlock(PParser.ProofBlockContext context)
716-
{
717-
var proofBlock = (ProofBlock) nodesToDeclarations.Get(context);
718-
proofBlock.Commands = context.proofBody().proofItem().Select(Visit).Cast<ProofCommand>().ToList();
719-
proofBlock.Commands.ForEach(x => x.ProofBlock = proofBlock.Name);
720-
return proofBlock;
721-
}
722-
723-
public override object VisitProveUsingCmd(PParser.ProveUsingCmdContext context)
724-
{
725-
var proofCmd = (ProofCommand) nodesToDeclarations.Get(context);
726-
var temporaryFunction = new Function(proofCmd.Name, context);
727-
temporaryFunction.Scope = CurrentScope.MakeChildScope();
728-
var exprVisitor = new ExprVisitor(temporaryFunction, Handler);
729-
List<IPExpr> premises = [];
730-
List<IPExpr> goals = [];
731-
List<IPExpr> excepts = context._excludes.Select(exprVisitor.Visit).ToList();
732-
if (context.premisesAll == null)
733-
{
734-
premises = context._premises.Select(exprVisitor.Visit).ToList();
735-
}
736-
else
737-
{
738-
premises = CurrentScope.AllDecls.OfType<Invariant>().Select(x => (IPExpr) new InvariantRefExpr(x, context)).ToList();
739-
}
740-
741-
if (context.goalsAll == null && context.goalsDefault == null)
742-
{
743-
goals = context._targets.Select(exprVisitor.Visit).ToList();
744-
}
745-
else if (context.goalsDefault != null)
746-
{
747-
goals = [new InvariantRefExpr(new Invariant(context), context)];
748-
}
749-
else
750-
{
751-
goals = CurrentScope.AllDecls.OfType<Invariant>().Select(x => (IPExpr) new InvariantRefExpr(x, context)).ToList();
752-
}
753-
754-
if (premises.Count == context._premises.Count)
755-
{
756-
proofCmd.Premises = premises.Zip(context._premises, (x, y) => ToInvariant(x, y)).SelectMany(x => x).ToList();
757-
}
758-
else
759-
{
760-
proofCmd.Premises = premises.SelectMany(x => ToInvariant(x, context)).ToList();
761-
}
762-
763-
if (goals.Count == context._targets.Count)
764-
{
765-
proofCmd.Goals = goals.Zip(context._targets, (x, y) => ToInvariant(x, y)).SelectMany(x => x).ToList();
766-
}
767-
else
768-
{
769-
proofCmd.Goals = goals.SelectMany(x => ToInvariant(x, context)).ToList();
770-
}
771-
772-
proofCmd.Excepts = excepts.Zip(context._excludes, (x, y) => ToInvariant(x, y)).SelectMany(x => x).ToList();
773-
proofCmd.Premises = proofCmd.Premises.Except(proofCmd.Excepts).ToList();
774-
proofCmd.Goals = proofCmd.Goals.Except(proofCmd.Excepts).ToList();
775-
776-
// prove A using B, ..., C means A -> B, ..., A -> C
777-
// If there is a cycle in the graph formed by all prove-using commands, then we should throw an error.
778-
// We could do this incrementally but the number of prove-using commands will probably be very small anyway
779-
// so we are just going to do a topological sort every time (https://gist.github.com/Sup3rc4l1fr4g1l1571c3xp14l1d0c10u5/3341dba6a53d7171fe3397d13d00ee3f)
780-
// TODO: using _ to pick out sub invariants?
781-
var nodes = new System.Collections.Generic.HashSet<string>();
782-
var edges = new System.Collections.Generic.HashSet<(string, string)>();
783-
foreach (var cmd in CurrentScope.ProofCommands)
784-
{
785-
if (cmd.Goals is null) continue;
786-
foreach (var source in cmd.Goals.Select(inv => inv.Name))
787-
{
788-
if (cmd.Premises is null) continue;
789-
foreach (var target in cmd.Premises.Select(inv => inv.Name))
790-
{
791-
nodes.Add(source);
792-
nodes.Add(target);
793-
edges.Add((source, target));
794-
}
795-
}
796-
}
797-
798-
// Set of all nodes with no incoming edges
799-
var S = new System.Collections.Generic.HashSet<string>(nodes.Where(n => edges.All(e => e.Item2.Equals(n) == false)));
800-
801-
// while S is non-empty do
802-
while (S.Any()) {
803-
804-
// remove a node n from S
805-
var n = S.First();
806-
S.Remove(n);
807-
808-
// for each node m with an edge e from n to m do
809-
foreach (var e in edges.Where(e => e.Item1.Equals(n)).ToList()) {
810-
var m = e.Item2;
811-
812-
// remove edge e from the graph
813-
edges.Remove(e);
814-
815-
// if m has no other incoming edges then
816-
if (edges.All(me => me.Item2.Equals(m) == false)) {
817-
S.Add(m);
818-
}
819-
}
820-
}
821-
822-
// if graph has edges then
823-
if (edges.Any()) {
824-
throw Handler.CyclicProof(proofCmd.SourceLocation, proofCmd);
825-
}
826-
827-
return proofCmd;
828-
}
829660

830661
public override object VisitAssumeOnStartDecl(PParser.AssumeOnStartDeclContext context)
831662
{
832663
// assume on start: body=Expr
833664
var assume = (AssumeOnStart) nodesToDeclarations.Get(context);
834-
835-
var temporaryFunction = new Function(assume.Name, context);
836-
temporaryFunction.Scope = CurrentScope.MakeChildScope();
837-
838-
var exprVisitor = new ExprVisitor(temporaryFunction, Handler);
839-
840-
var body = exprVisitor.Visit(context.body);
841-
842-
if (!PrimitiveType.Bool.IsSameTypeAs(body.Type))
843-
{
844-
throw Handler.TypeMismatch(context.body, body.Type, PrimitiveType.Bool);
845-
}
846-
847-
assume.Body = body;
848-
665+
// body will be handled in a later stage
849666
return assume;
850667
}
851668

0 commit comments

Comments
 (0)