feat: disallow patterns with choice nodes

@Kha Patterns with nested "choice" nodes produce counterintuitive
behavior, and hard to understand errors. They only work when we have
the exact same overloads at macro definition and application time.

Here is a funky example
```
syntax [myAdd] term "++":65 term:65 : term    -- overloads "++"
/- The following `macro_rules` manages to eliminate "choice" node by using the
   explicitly provided node kind. -/
macro_rules [myAdd] `($a ++ $b) => `($a + $b)

check (1:Nat) ++ 2 -- works as expected

syntax "FOO!" term : term

/- Before this commit, the following pattern was accepted,
   and it contained a "choice" node for `++`. It is a `myAdd` or
   `HasAppend.append`.

   Now, this kind of pattern is rejected since it contains a nested
   "choice" -/
macro_rules `(FOO! $a ++ $b) => `($a ++ $b)

/- Before this commit, the following command worked because
   it contained a "choice" node similar to the one at macro definition
   time. -/
check FOO! [1, 2] ++ [2, 3]

-- Now, we have 3 overloads for "++".
syntax [myAppend] term "++":65 term:65 : term

-- The `macro_rules` fails here.
```

This commit fixes this weird behavior by disallowing "choice" nodes in
patterns.
This commit is contained in:
Leonardo de Moura 2020-01-26 14:55:55 -08:00
parent 4e580e78bc
commit 145bd50903
2 changed files with 12 additions and 2 deletions

View file

@ -330,8 +330,11 @@ alts ← alts.getArgs.getSepElems.mapM $ fun alt => do {
pat ← if pats.getArgs.size == 1 then pure $ pats.getArg 0
else throwError stx "match_syntax: expected exactly one pattern per alternative";
let pat := if pat.isOfKind `Lean.Parser.Term.stxQuot then pat.setArg 1 $ elimAntiquotChoices $ pat.getArg 1 else pat;
let rhs := alt.getArg 2;
pure ([pat], rhs)
match pat.find? $ fun stx => stx.getKind == choiceKind with
| some choiceStx => throwError choiceStx "invalid pattern, nested syntax has multiple interpretations"
| none =>
let rhs := alt.getArg 2;
pure ([pat], rhs)
};
-- letBindRhss (compileStxMatch stx [discr]) alts.toList []
compileStxMatch stx [discr] alts.toList

View file

@ -750,6 +750,13 @@ match stx.isTermId? relaxed with
| some (id, opt) => if opt.isNone then some id else none
| none => none
partial def findAux (p : Syntax → Bool) : Syntax → Option Syntax
| stx@(Syntax.node _ args) => if p stx then some stx else args.findSome? findAux
| stx => if p stx then some stx else none
def find? (stx : Syntax) (p : Syntax → Bool) : Option Syntax :=
findAux p stx
end Syntax
end Lean