-
Notifications
You must be signed in to change notification settings - Fork 446
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: unused
match
-syntax alternatives are silently ignored
closes #1371
- Loading branch information
1 parent
feb7127
commit 37af11a
Showing
8 changed files
with
92 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -385,9 +385,11 @@ private partial def getHeadInfo (alt : Alt) : TermElabM HeadInfo := | |
let contents := if contents.size == 1 | ||
then contents[0]! | ||
else mkNullNode contents | ||
-- We use `no_error_if_unused%` in auxiliary `match`-syntax to avoid spurious error messages, | ||
-- the outer `match` is checking for unused alternatives | ||
`(match ($(discrs).sequenceMap fun | ||
| `($contents) => some $tuple | ||
| _ => none) with | ||
| `($contents) => no_error_if_unused% some $tuple | ||
| _ => no_error_if_unused% none) with | ||
| some $resId => $yes | ||
| none => $no) | ||
} | ||
|
@@ -533,7 +535,7 @@ private partial def compileStxMatch (discrs : List Term) (alts : List Alt) : Ter | |
-- group undecided alternatives in a new default case `| discr2, ... => match discr, discr2, ... with ...` | ||
let vars ← discrs.mapM fun _ => withFreshMacroScope `(discr) | ||
let pats := List.replicate newDiscrs.length (Unhygienic.run `(_)) ++ vars | ||
let alts ← undecidedAlts.mapM fun alt => `(matchAltExpr| | $(alt.1.toArray),* => $(alt.2)) | ||
let alts ← undecidedAlts.mapM fun alt => `(matchAltExpr| | $(alt.1.toArray),* => no_error_if_unused% $(alt.2)) | ||
let rhs ← `(match discr, $[$(vars.toArray):term],* with $alts:matchAlt*) | ||
yesAlts := yesAlts.push (pats, rhs) | ||
withFreshMacroScope $ compileStxMatch (newDiscrs ++ discrs) yesAlts.toList) | ||
|
@@ -543,15 +545,77 @@ private partial def compileStxMatch (discrs : List Term) (alts : List Alt) : Ter | |
`(let discr := $discr; $stx) | ||
| _, _ => unreachable! | ||
|
||
abbrev IdxSet := Std.HashSet Nat | ||
|
||
/-- | ||
Given `rhss` the right-hand-sides of a `match`-syntax notation, | ||
We tag them with with fresh identifiers `alt_idx`. We use them to detect whether an alternative | ||
has been used or not. | ||
The result is a triple `(altIdxMap, ignoreIfUnused, rhssNew)` where | ||
- `altIdxMap` is a mapping from the `alt_idx` identifiers to right-hand-side indices. | ||
That is, the map contains the entry `alt_idx ↦ i` if `alt_idx` was used to mark `rhss[i]`. | ||
- `i ∈ ignoreIfUnused` if `rhss[i]` is marked with `no_error_if_unused%` | ||
- `rhssNew` is the updated array of right-hand-sides. | ||
-/ | ||
private def markRhss (rhss : Array Term) : TermElabM (NameMap Nat × IdxSet × Array Term) := do | ||
let mut altIdxMap : NameMap Nat := {} | ||
let mut ignoreIfUnused : IdxSet := {} | ||
let mut rhssNew := #[] | ||
for rhs in rhss do | ||
match rhs with | ||
| `(no_error_if_unused% $_ ) => ignoreIfUnused := ignoreIfUnused.insert rhssNew.size | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
leodemoura
Author
Member
|
||
| _ => pure () | ||
let (idx, rhs) ← withFreshMacroScope do | ||
let idx ← `(alt_idx) | ||
let rhs ← `(alt_idx $rhs) | ||
return (idx, rhs) | ||
altIdxMap := altIdxMap.insert idx.getId rhssNew.size | ||
rhssNew := rhssNew.push rhs | ||
return (altIdxMap, ignoreIfUnused, rhssNew) | ||
|
||
/-- | ||
Given the mapping `idxMap` built using `markRhss`, and `stx` the resulting syntax after expanding `match`-syntax, | ||
return the pair `(stxNew, usedSet)`, where `stxNew` is `stx` after removing the `alt_idx` markers in `idxMap`, | ||
and `i ∈ usedSet` if `stx` contains an `alt_idx` s.t. `alt_idx ↦ i` is in `idxMap`. | ||
That is, `usedSet` contains the index of the used match-syntax right-hand-sides. | ||
-/ | ||
private partial def findUsedAlts (stx : Syntax) (altIdxMap : NameMap Nat) : TermElabM (Syntax × IdxSet) := do | ||
go stx |>.run {} | ||
where | ||
go (stx : Syntax) : StateRefT IdxSet TermElabM Syntax := do | ||
match stx with | ||
| `($id:ident $rhs:term) => | ||
if let some idx := altIdxMap.find? id.getId then | ||
modify fun s => s.insert idx | ||
return rhs | ||
| _ => pure () | ||
match stx with | ||
| .node info kind cs => return .node info kind (← cs.mapM go) | ||
| _ => return stx | ||
|
||
/-- | ||
Check whether `stx` has unused alternatives, and remove the auxiliary `alt_idx` markers from it (see `markRhss`). | ||
The parameter `alts` provides position information for alternatives. | ||
`altIdxMap` and `ignoreIfUnused` are the map and set built using `markRhss`. | ||
-/ | ||
private def checkUnusedAlts (stx : Syntax) (alts : Array Syntax) (altIdxMap : NameMap Nat) (ignoreIfUnused : IdxSet) : TermElabM Syntax := do | ||
let (stx, used) ← findUsedAlts stx altIdxMap | ||
for i in [:alts.size] do | ||
unless used.contains i || ignoreIfUnused.contains i do | ||
logErrorAt alts[i]! s!"redundant alternative #{i+1}" | ||
return stx | ||
|
||
def match_syntax.expand (stx : Syntax) : TermElabM Syntax := do | ||
match stx with | ||
| `(match $[$discrs:term],* with $[| $[$patss],* => $rhss]*) => do | ||
| `(match $[$discrs:term],* with $[|%$alt $[$patss],* => $rhss]*) => do | ||
if !patss.any (·.any (fun | ||
| `($_@$pat) => pat.raw.isQuot | ||
| pat => pat.raw.isQuot)) then | ||
-- no quotations => fall back to regular `match` | ||
throwUnsupportedSyntax | ||
let (altIdxMap, ignoreIfUnused, rhss) ← markRhss rhss | ||
let stx ← compileStxMatch discrs.toList (patss.map (·.toList) |>.zip rhss).toList | ||
let stx ← checkUnusedAlts stx alt altIdxMap ignoreIfUnused | ||
trace[Elab.match_syntax.result] "{stx}" | ||
return stx | ||
| _ => throwUnsupportedSyntax | ||
|
@@ -576,6 +640,11 @@ def match_syntax.expand (stx : Syntax) : TermElabM Syntax := do | |
@[builtinTermElab «match»] def elabMatchSyntax : TermElab := | ||
adaptExpander match_syntax.expand | ||
|
||
@[builtinTermElab noErrorIfUnused] def elabNoErrorIfUnused : TermElab := fun stx expectedType? => | ||
match stx with | ||
| `(no_error_if_unused% $term) => elabTerm term expectedType? | ||
| _ => throwUnsupportedSyntax | ||
|
||
builtin_initialize | ||
registerTraceClass `Elab.match_syntax | ||
registerTraceClass `Elab.match_syntax.result | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
def f (stx : Lean.Syntax) := | ||
match stx with | ||
| `($f $a) => 1 | ||
| `($_) => 2 | ||
| `($f $b) => 3 | ||
| _ => "hello" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
1371.lean:5:2-5:3: error: redundant alternative #3 | ||
1371.lean:6:2-6:3: error: redundant alternative #4 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@leodemoura We could also suppress the error by default on alternatives with synthetic positions, i.e. not directly written by the user - just like we ignore unused variables with macro scopes