Check free variables in assertions (#1737)
authorAndrew Reynolds <andrew.j.reynolds@gmail.com>
Sun, 8 Apr 2018 20:23:20 +0000 (15:23 -0500)
committerGitHub <noreply@github.com>
Sun, 8 Apr 2018 20:23:20 +0000 (15:23 -0500)
src/expr/expr_template.cpp
src/expr/expr_template.h
src/expr/node.cpp
src/expr/node.h
src/parser/smt2/Smt2.g
src/theory/quantifiers/single_inv_partition.cpp
src/theory/theory_engine.cpp

index 010b36e94c7a79850d311295855ac4a0966545a0..f812be5a3ca280e8ccfb37897b6409afb906d740 100644 (file)
@@ -530,6 +530,13 @@ bool Expr::isConst() const {
   return d_node->isConst();
 }
 
+bool Expr::hasFreeVariable() const
+{
+  ExprManagerScope ems(*this);
+  Assert(d_node != NULL, "Unexpected NULL expression pointer!");
+  return d_node->hasFreeVar();
+}
+
 void Expr::toStream(std::ostream& out, int depth, bool types, size_t dag,
                     OutputLanguage language) const {
   ExprManagerScope ems(*this);
index cb4534c08af134180eb478cd1731e78cfa25e08d..3a27fca25a768356968006d895a71994e5384771 100644 (file)
@@ -526,6 +526,13 @@ public:
    */
   bool isConst() const;
 
+  /**
+   * Check if this expression contains a free variable.
+   *
+   * @return true if this node contains a free variable.
+   */
+  bool hasFreeVariable() const;
+
   /* A note on isAtomic() and isAtomicFormula() (in CVC3 parlance)..
    *
    * It has been decided for now to hold off on implementations of
index b41014f9c50d93a0b15acd687dba2312ca78d449..cb61362c599ca4eb5fc97bdea88e7fbb6d18a220 100644 (file)
@@ -137,9 +137,73 @@ bool NodeTemplate<ref_count>::hasBoundVar() {
   return getAttribute(HasBoundVarAttr());
 }
 
+template <bool ref_count>
+bool NodeTemplate<ref_count>::hasFreeVar()
+{
+  assertTNodeNotExpired();
+  std::unordered_set<TNode, TNodeHashFunction> bound_var;
+  std::unordered_map<TNode, bool, TNodeHashFunction> visited;
+  std::vector<TNode> visit;
+  TNode cur;
+  visit.push_back(*this);
+  do
+  {
+    cur = visit.back();
+    visit.pop_back();
+    // can skip if it doesn't have a bound variable
+    if (!cur.hasBoundVar())
+    {
+      continue;
+    }
+    Kind k = cur.getKind();
+    bool isQuant = k == kind::FORALL || k == kind::EXISTS || k == kind::LAMBDA
+                   || k == kind::CHOICE;
+    std::unordered_map<TNode, bool, TNodeHashFunction>::iterator itv =
+        visited.find(cur);
+    if (itv == visited.end())
+    {
+      if (k == kind::BOUND_VARIABLE)
+      {
+        if (bound_var.find(cur) == bound_var.end())
+        {
+          return true;
+        }
+      }
+      else if (isQuant)
+      {
+        for (const TNode& cn : cur[0])
+        {
+          // should not shadow
+          Assert(bound_var.find(cn) == bound_var.end());
+          bound_var.insert(cn);
+        }
+        visit.push_back(cur);
+      }
+      // must visit quantifiers again to clean up below
+      visited[cur] = !isQuant;
+      for (const TNode& cn : cur)
+      {
+        visit.push_back(cn);
+      }
+    }
+    else if (!itv->second)
+    {
+      Assert(isQuant);
+      for (const TNode& cn : cur[0])
+      {
+        bound_var.erase(cn);
+      }
+      visited[cur] = true;
+    }
+  } while (!visit.empty());
+  return false;
+}
+
 template bool NodeTemplate<true>::isConst() const;
 template bool NodeTemplate<false>::isConst() const;
 template bool NodeTemplate<true>::hasBoundVar();
 template bool NodeTemplate<false>::hasBoundVar();
+template bool NodeTemplate<true>::hasFreeVar();
+template bool NodeTemplate<false>::hasFreeVar();
 
 }/* CVC4 namespace */
index e1b9795705d2dd78779b7c5bcd2809ffcb964acb..14630bae10db0b5df78aea95a49d1193a644550b 100644 (file)
@@ -435,6 +435,12 @@ public:
    */
   bool hasBoundVar();
 
+  /**
+   * Returns true iff this node contains a free variable.
+   * @return true iff this node contains a free variable.
+   */
+  bool hasFreeVar();
+
   /**
    * Convert this Node into an Expr using the currently-in-scope
    * manager.  Essentially this is like an "operator Expr()" but we
index 2c26c824f9bbd297e42ea2d343040f788a2d7990..ae9d304f164b1cf2a735f0d15d3cf14f886d9d5b 100644 (file)
@@ -429,6 +429,17 @@ command [std::unique_ptr<CVC4::Command>* cmd]
         csen->setMuted(true);
         PARSER_STATE->preemptCommand(csen);
       }
+      // if sygus, check whether it has a free variable
+      // this is because, due to the sygus format, one can write assertions
+      // that have free function variables in them
+      if (PARSER_STATE->sygus())
+      {
+        if (expr.hasFreeVariable())
+        {
+          PARSER_STATE->parseError("Assertion has free variable. Perhaps you "
+                                   "meant constraint instead of assert?");
+        }
+      }
     }
   | /* check-sat */
     CHECK_SAT_TOK { PARSER_STATE->checkThatLogicIsSet(); }
index 890b7fcd3cff285dcc76529497f2e79e7208298c..0a0dac4ba7cb092ac40fb2469b58fd87fd8a1196 100644 (file)
@@ -250,37 +250,28 @@ bool SingleInvocationPartition::init(std::vector<Node>& funcs,
         // now must check if it has other bound variables
         std::vector<Node> bvs;
         TermUtil::getBoundVars(cr, bvs);
-        if (bvs.size() > d_si_vars.size())
+        // bound variables must be contained in the single invocation variables
+        for (const Node& bv : bvs)
         {
-          // getBoundVars also collects functions in the rare case that we are
-          // synthesizing a function with 0 arguments
-          // take these into account below.
-          unsigned n_const_synth_fun = 0;
-          for (unsigned j = 0; j < bvs.size(); j++)
+          if (std::find(d_si_vars.begin(), d_si_vars.end(), bv)
+              == d_si_vars.end())
           {
-            if (std::find(d_input_funcs.begin(), d_input_funcs.end(), bvs[j])
-                != d_input_funcs.end())
+            // getBoundVars also collects functions in the rare case that we are
+            // synthesizing a function with 0 arguments, take this into account
+            // here.
+            if (std::find(d_input_funcs.begin(), d_input_funcs.end(), bv)
+                == d_input_funcs.end())
             {
-              n_const_synth_fun++;
+              Trace("si-prt")
+                  << "...not ground single invocation." << std::endl;
+              ngroundSingleInvocation = true;
+              singleInvocation = false;
             }
           }
-          if (bvs.size() - n_const_synth_fun > d_si_vars.size())
-          {
-            Trace("si-prt") << "...not ground single invocation." << std::endl;
-            ngroundSingleInvocation = true;
-            singleInvocation = false;
-          }
-          else
-          {
-            Trace("si-prt") << "...ground single invocation : success, after "
-                               "removing 0-arg synth functions."
-                            << std::endl;
-          }
         }
-        else
+        if (singleInvocation)
         {
-          Trace("si-prt") << "...ground single invocation : success."
-                          << std::endl;
+          Trace("si-prt") << "...ground single invocation" << std::endl;
         }
       }
       else
index f3489f5ade63b2bc9afe3daa6a8a88698e4d7e6c..de71a8b5e9974826c07796c96e2cf6c51fd6ccca 100644 (file)
@@ -393,6 +393,8 @@ void TheoryEngine::preRegister(TNode preprocessed) {
         d_sharedTerms.addEqualityToPropagate(preprocessed);
       }
 
+      // the atom should not have free variables
+      Assert(!preprocessed.hasFreeVar());
       // Pre-register the terms in the atom
       Theory::Set theories = NodeVisitor<PreRegisterVisitor>::run(d_preRegistrationVisitor, preprocessed);
       theories = Theory::setRemove(THEORY_BOOL, theories);