Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: Compute matching patterns for automatic induction #5835

Open
wants to merge 47 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
8c4c4a6
chore: Improve trigger/induction code
RustanLeino Oct 12, 2024
e62d2b6
Compute triggers for automatic induction
RustanLeino Oct 12, 2024
ad1c724
Improve induction heuristics
RustanLeino Oct 12, 2024
29d715a
chore: Change “ghost method” to “lemma”
RustanLeino Oct 12, 2024
4cfbe21
chore: Improve C#
RustanLeino Oct 14, 2024
b17c662
Also consider codatatype equality as a greatest focal predicate
RustanLeino Oct 14, 2024
cee8d58
Show tooltips for any quantifier rewrite substitutions
RustanLeino Oct 14, 2024
dda37cb
feat!: Auto-induction for twostate lemmas but not ghost methods
RustanLeino Oct 14, 2024
fa79ba3
fix: Don’t consider arrow-typed variables for auto induction
RustanLeino Oct 16, 2024
0028a3a
feat: allow ternary expressions in triggers
RustanLeino Oct 16, 2024
394c635
Compute and use triggers for induction hypotheses
RustanLeino Oct 16, 2024
659d4ba
Tooltip named expressions from trigger selection
RustanLeino Oct 16, 2024
6b5b207
chore: Remove deprecated semi-colons
RustanLeino Oct 16, 2024
3fc56d3
Add tests for ternary expressions in triggers
RustanLeino Oct 16, 2024
73d2d61
Adjust resource count in test
RustanLeino Oct 16, 2024
73406c6
Merge branch 'master' into triggers-for-auto-induction
RustanLeino Oct 16, 2024
f4815f4
Add release notes
RustanLeino Oct 16, 2024
e4e4ce8
Help proof
RustanLeino Oct 17, 2024
3a0f1c0
Update test, which no longer exhausts resources
RustanLeino Oct 17, 2024
703c661
Improve proofs
RustanLeino Oct 17, 2024
54e9ddb
Fix typo in method name
RustanLeino Oct 20, 2024
b46f0dc
Remove unnecessary assertion
RustanLeino Oct 20, 2024
022685c
Remove unnecessary $’s
RustanLeino Oct 20, 2024
4cb3f5a
Improve C# and comments
RustanLeino Oct 20, 2024
a213285
Revert tooltip printing of trigger named expressions
RustanLeino Oct 20, 2024
72c15fe
Improve and document methods that compute/report induction triggers
RustanLeino Oct 20, 2024
704af8c
Incomplete attempt at specifying behavior
RustanLeino Oct 20, 2024
9e9dd80
doc: Align :induction documentation with actual behavior
cpitclaudel Oct 23, 2024
8a792b1
Remove unnecessary $s
cpitclaudel Oct 23, 2024
7c6ab84
Move :inductionPattern attribute generation to ComputeInductionTriggers
cpitclaudel Oct 23, 2024
766ac5b
Allow users to disable trigger generation with an empty inductionTrigger
cpitclaudel Oct 23, 2024
a3702d2
Update documentation
cpitclaudel Oct 23, 2024
ffe54af
Add one more test for induction triggers
cpitclaudel Oct 23, 2024
8c7b06c
Document {:inductionTrigger}
cpitclaudel Oct 24, 2024
769d6cd
Always report “inductionTrigger”, not just in DEBUG build
RustanLeino Oct 30, 2024
2c7fcc9
Fix format of expected test output
RustanLeino Oct 30, 2024
62e771c
Merge branch 'master' into triggers-for-auto-induction
RustanLeino Oct 30, 2024
af0223b
Use underscore name for generated attributes
RustanLeino Oct 31, 2024
6cdaa08
Update tests
RustanLeino Oct 31, 2024
a1149c9
Update tests and expected output
RustanLeino Oct 31, 2024
c447ffa
Fix previous code edit
RustanLeino Oct 31, 2024
3d8d0e7
Merge branch 'master' into triggers-for-auto-induction
RustanLeino Oct 31, 2024
63683d1
Update standard libraries
RustanLeino Oct 31, 2024
808a2b3
Merge branch 'master' into triggers-for-auto-induction
RustanLeino Oct 31, 2024
43bdbd3
Merge branch 'master' into triggers-for-auto-induction
RustanLeino Oct 31, 2024
3652ff3
Update standard library
RustanLeino Oct 31, 2024
b070751
Update standard library
RustanLeino Nov 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions Source/DafnyCore/Rewriters/InductionRewriter.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using JetBrains.Annotations;
using static Microsoft.Dafny.RewriterErrors;

Expand Down Expand Up @@ -183,6 +184,23 @@ void ComputeInductionVariables<TVarType>(IToken tok, List<TVarType> boundVars, E
}

if (inductionVariables.Count != 0) {
if (lemma != null) {
var triggers = ComputeInductionTriggers(inductionVariables, body, lemma.EnclosingClass.EnclosingModuleDefinition);
if (triggers.Count == 0) {
var msg = "omitting automatic induction because of lack of triggers";
if (args != null) {
Reporter.Warning(MessageSource.Rewriter, GenericErrors.ErrorId.none, tok, msg);
} else {
Reporter.Info(MessageSource.Rewriter, tok, msg);
}
return;
}

foreach (var trigger in triggers) {
attributes = new Attributes("_inductionPattern", trigger, attributes);
}
}

// We found something usable, so let's record that in an attribute
attributes = new Attributes("_induction", inductionVariables, attributes);
// And since we're inferring something, let's also report that in a hover text.
Expand All @@ -195,6 +213,49 @@ void ComputeInductionVariables<TVarType>(IToken tok, List<TVarType> boundVars, E
}
}

/// <summary>
/// Obtain and return matching patterns for
/// (forall inductionVariables :: body)
/// If there aren't any, then return null.
/// </summary>
List<List<Expression>> ComputeInductionTriggers(List<Expression> inductionVariables, Expression body, ModuleDefinition moduleDefinition) {
Contract.Requires(inductionVariables.Count != 0);

// Construct a quantifier, because that's what the trigger-generating machinery expects.
cpitclaudel marked this conversation as resolved.
Show resolved Hide resolved
// We start by creating a new BoundVar for each ThisExpr-or-IdentifierExpr in "inductionVariables".
var boundVars = new List<BoundVar>();
var substMap = new Dictionary<IVariable, Expression>();
var reverseSubstMap = new Dictionary<IVariable, Expression>();
Expression receiverReplacement = null;
foreach (var inductionVariableExpr in inductionVariables) {
var tok = inductionVariableExpr.tok;
BoundVar boundVar;
if (inductionVariableExpr is IdentifierExpr identifierExpr) {
boundVar = new BoundVar(tok, identifierExpr.Var.Name, identifierExpr.Var.Type);
substMap.Add(identifierExpr.Var, new IdentifierExpr(tok, boundVar));
} else {
Contract.Assert(inductionVariableExpr is ThisExpr);
boundVar = new BoundVar(tok, "this", inductionVariableExpr.Type);
receiverReplacement = new IdentifierExpr(tok, boundVar);
}
boundVars.Add(boundVar);
reverseSubstMap.Add(boundVar, inductionVariableExpr);
}

var substituter = new Substituter(receiverReplacement, substMap, new Dictionary<TypeParameter, Type>());
var quantifier = new ForallExpr(body.tok, body.RangeToken, boundVars, null, substituter.Substitute(body), null) {
Type = Type.Bool
};

var finder = new Triggers.QuantifierCollector(Reporter);
var triggersCollector = new Triggers.TriggersCollector(finder.exprsInOldContext, Reporter.Options, moduleDefinition);
var quantifierCollection = new Triggers.ComprehensionTriggerGenerator(quantifier, Enumerable.Repeat(quantifier, 1), Reporter);
quantifierCollection.ComputeTriggers(triggersCollector);
var triggers = quantifierCollection.GetTriggers();
var reverseSubstituter = new Substituter(null, reverseSubstMap, new Dictionary<TypeParameter, Type>());
return triggers.ConvertAll(trigger => trigger.ConvertAll(reverseSubstituter.Substitute));
}

class InductionVisitor : BottomUpVisitor {
readonly InductionRewriter IndRewriter;

Expand Down
14 changes: 14 additions & 0 deletions Source/DafnyCore/Triggers/ComprehensionTriggerGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -230,5 +230,19 @@ internal void CommitTriggers(SystemModuleManager systemModuleManager) {
triggerWriter.CommitTrigger(reporter, partWriters.Count > 1 ? index : null, systemModuleManager);
}
}

public List<List<Expression>> GetTriggers() {
var triggers = new List<List<Expression>>();
foreach (var triggerWriter in partWriters) {
foreach (var triggerTerms in triggerWriter.Candidates) {
var trigger = new List<Expression>();
foreach (var triggerTerm in triggerTerms.Terms) {
trigger.Add(triggerTerm.Expr);
}
cpitclaudel marked this conversation as resolved.
Show resolved Hide resolved
triggers.Add(trigger);
}
}
cpitclaudel marked this conversation as resolved.
Show resolved Hide resolved
return triggers;
}
}
}
15 changes: 9 additions & 6 deletions Source/DafnyCore/Verifier/BoogieGenerator.Methods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -782,14 +782,17 @@ private StmtList TrMethodBody(Method m, BoogieStmtListBuilder builder, Variables
null, null, false, true);
};

var triggers = Attributes.FindAllExpressions(m.Attributes, "_inductionPattern");
#if VERIFY_CORRECTNESS_OF_TRANSLATION_FORALL_STATEMENT_RANGE
var definedness = new BoogieStmtListBuilder(this, options);
var exporter = new BoogieStmtListBuilder(this, options);
TrForallStmtCall(m.tok, parBoundVars, parRange, decrCheck, null, recursiveCall, definedness, exporter, localVariables, etran);
// All done, so put the two pieces together
builder.Add(new Bpl.IfCmd(m.tok, null, definedness.Collect(m.tok), null, exporter.Collect(m.tok)));
var definedness = new BoogieStmtListBuilder(this, options, builder.Context);
var exporter = new BoogieStmtListBuilder(this, options, builder.Context);
TrForallStmtCall(m.tok, parBoundVars, parBounds, parRange, decrCheck, null, triggers, recursiveCall, definedness,
exporter, localVariables, etran);
// All done, so put the two pieces together
builder.Add(new Bpl.IfCmd(m.tok, null, definedness.Collect(m.tok), null, exporter.Collect(m.tok)));
#else
TrForallStmtCall(m.tok, parBoundVars, parBounds, parRange, decrCheck, null, recursiveCall, null, builder, localVariables, etran);
TrForallStmtCall(m.tok, parBoundVars, parBounds, parRange, decrCheck, null, triggers, recursiveCall, null,
builder, localVariables, etran);
#endif
}
// translate the body of the method
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,14 @@ private void TrForallStmt(BoogieStmtListBuilder builder, Variables locals, Expre
} else {
var s0 = (CallStmt)forallStmt.S0;
if (Attributes.Contains(forallStmt.Attributes, "_trustWellformed")) {
TrForallStmtCall(forallStmt.Tok, forallStmt.BoundVars, forallStmt.Bounds, forallStmt.Range, null, forallStmt.EffectiveEnsuresClauses, s0, null, builder, locals, etran);
TrForallStmtCall(forallStmt.Tok, forallStmt.BoundVars, forallStmt.Bounds, forallStmt.Range, null,
forallStmt.EffectiveEnsuresClauses, null, s0, null, builder, locals, etran);
} else {
var definedness = new BoogieStmtListBuilder(this, options, builder.Context);
DefineFuelConstant(forallStmt.Tok, forallStmt.Attributes, definedness, etran);
var exporter = new BoogieStmtListBuilder(this, options, builder.Context);
TrForallStmtCall(forallStmt.Tok, forallStmt.BoundVars, forallStmt.Bounds, forallStmt.Range, null, forallStmt.EffectiveEnsuresClauses, s0, definedness, exporter, locals, etran);
TrForallStmtCall(forallStmt.Tok, forallStmt.BoundVars, forallStmt.Bounds, forallStmt.Range, null,
forallStmt.EffectiveEnsuresClauses, null, s0, definedness, exporter, locals, etran);
// All done, so put the two pieces together
builder.Add(new Bpl.IfCmd(forallStmt.Tok, null, definedness.Collect(forallStmt.Tok), null, exporter.Collect(forallStmt.Tok)));
}
Expand All @@ -71,13 +73,14 @@ private void TrForallStmt(BoogieStmtListBuilder builder, Variables locals, Expre


void TrForallStmtCall(IToken tok, List<BoundVar> boundVars, List<BoundedPool> bounds,
Expression range, ExpressionConverter additionalRange, List<Expression> forallExpressions, CallStmt s0,
Expression range, ExpressionConverter additionalRange, List<Expression> forallExpressions, List<List<Expression>> triggers, CallStmt s0,
BoogieStmtListBuilder definedness, BoogieStmtListBuilder exporter, Variables locals, ExpressionTranslator etran) {
Contract.Requires(tok != null);
Contract.Requires(boundVars != null);
Contract.Requires(bounds != null);
Contract.Requires(range != null);
// additionalRange is allowed to be null
Contract.Requires(forallExpressions == null || triggers == null || triggers.Count == 0);
Contract.Requires(s0 != null);
// definedness is allowed to be null
Contract.Requires(exporter != null);
Expand Down Expand Up @@ -203,7 +206,18 @@ void TrForallStmtCall(IToken tok, List<BoundVar> boundVars, List<BoundedPool> bo
p = Substitute(ens.E, receiver, argsSubstMap, s0.MethodSelect.TypeArgumentSubstitutionsWithParents()); // substitute the call's actuals for the method's formals
post = BplAnd(post, callEtran.TrExpr(p));
}

tr = null;
cpitclaudel marked this conversation as resolved.
Show resolved Hide resolved
if (triggers != null) {
foreach (var trigger in triggers) {
Contract.Assert(trigger.Count != 0);
var terms = trigger.ConvertAll(expr => {
expr = Substitute(expr, receiver, argsSubstMap, s0.MethodSelect.TypeArgumentSubstitutionsWithParents());
return callEtran.TrExpr(expr);
});
tr = new Trigger(trigger[0].tok, true, terms, tr);
}
}
}

// TRIG (forall $ih#s0#0: Seq :: $Is($ih#s0#0, TSeq(TChar)) && $IsAlloc($ih#s0#0, TSeq(TChar), $initHeapForallStmt#0) && Seq#Length($ih#s0#0) != 0 && Seq#Rank($ih#s0#0) < Seq#Rank(s#0) ==> (forall i#2: int :: true ==> LitInt(0) <= i#2 && i#2 < Seq#Length($ih#s0#0) ==> char#ToInt(_module.CharChar.MinChar($LS($LZ), $Heap, this, $ih#s0#0)) <= char#ToInt($Unbox(Seq#Index($ih#s0#0, i#2)): char)))
Expand Down