import Lean
import Lake

open Lean Parser Meta Elab Do

set_option linter.unusedVariables false
set_option backward.do.legacy false

/-!
Miscellaneous tests for the new `do` elaborator.
Many of these are extracted from our code base.
-/

-- Test: Try/catch with let mut and match
#check Id.run <| ExceptT.run (ε:=String) (α := Fin 17) do
  let mut x := 0
  try
    if true then
      x := 10
      match h : x with
      | 2 => return ⟨x, by grind⟩
      | _ => return 0
    else
      x := 5
  catch e =>
    x := x + 1
  return ⟨3, by decide⟩

example : Unit := (Id.run do let n ← if true then pure 3 else pure 42)

-- Another complicated `match` that would need to generalize the join point type if it was dependent
example (x : Nat) : Id (Fin (x + 2)) := do
  let mut a := 1
  let mut b := 2
  let mut c := 3
  let r : Fin (x + 1) ←
    if a = 1 then
      match x with
      | 0 => a := a + 1; pure 0
      | _ => pure 0
    else
      b := b + 1; pure 0
  if a + b + c < 10 then
    pure 0
  else
    pure ⟨r + 1, by grind⟩

-- Full-blown match + try/catch, early return and break/continue
example (p n : Nat) : Except String (Fin (2 * p + 2)) := Id.run <| ExceptT.run do
  let mut a := 0
  for i in [n:n+10].toList do
    try
      have y' : Fin (p + 1) := ⟨0, by grind⟩
      let mut y₁ : Fin (p + 1) := ⟨0, by grind⟩
      let y₂ : Fin (p + 1) ←
        match h : p with
        | z + 1 => y₁ := ⟨1, by grind⟩; return ⟨3, by grind⟩
        | _     => pure ⟨0, by grind⟩
      if i = 5 then return ⟨y₁.val + y₂.val, by grind⟩
      if i = 15 then break
      if i = 25 then continue
      if i = 35 then throw "error"
      a := a + i
    catch _ =>
      a := a + 1
  return 0

/--
error: The `do` elaborator does not support custom motives.
If you want a dependent match, try `(dependent := true)`.
If you want to provide an expected type, do so via type ascription on the bind.
-/
#guard_msgs (error) in
example (x : Nat) := Id.run (α := Fin (2 * x + 2)) do
  have y' : Fin (x + 1) := ⟨0, by grind⟩
  let mut y₁ : Fin (x + 1) := ⟨0, by grind⟩
  let y₂ ←
    match (motive := ∀ x, Fin (x + 1)) x with
    | z + 1 => pure ⟨1, by grind⟩
    | _     => pure ⟨0, by grind⟩
  return ⟨y₁.val + y₂.val, by grind⟩

-- Test: elabToSyntax and postponement
/--
error: Invalid match expression: The type of pattern variable 'y' contains metavariables:
  ?m.13
-/
#guard_msgs (error) in
example := Id.run do
  let mut x := 0
  if let some y := none then
    pure 1
  else
    pure 2
/--
error: typeclass instance problem is stuck
  Pure ?m.8

Note: Lean will not try to resolve this typeclass instance problem because the type argument to `Pure` is a metavariable. This argument must be fully determined before Lean will try to resolve the typeclass.

Hint: Adding type annotations and supplying implicit arguments to functions can give Lean more information for typeclass resolution. For example, if you have a variable `x` that you intend to be a `Nat`, but Lean reports it as having an unresolved type like `?m`, replacing `x` with `(x : Nat)` can get typeclass resolution un-stuck.
-/
#guard_msgs (error) in
example := do return 42
/--
error: typeclass instance problem is stuck
  Bind ?m.22

Note: Lean will not try to resolve this typeclass instance problem because the type argument to `Bind` is a metavariable. This argument must be fully determined before Lean will try to resolve the typeclass.

Hint: Adding type annotations and supplying implicit arguments to functions can give Lean more information for typeclass resolution. For example, if you have a variable `x` that you intend to be a `Nat`, but Lean reports it as having an unresolved type like `?m`, replacing `x` with `(x : Nat)` can get typeclass resolution un-stuck.
-/
#guard_msgs (error) in
example := do let x ← ?z; ?y

-- This tests that inferred types are correctly propagated outwards.
example {e : Id Nat} := do if true then e else pure 13
-- We do want to be able to `#check` the following example (including the last `pure`) without an
-- expected monad, ...
#check do let mut x : Nat := 0; if true then {x := x + 1} else {pure ()}; pure x
-- As well as fully resolve all instances in the following example where the expected monad is
-- known. The next example wouldn't work without `Id.run`.
example := Id.run do let mut x : Nat := 0; if true then {x := x + 1} else {pure ()}; pure x

/-- error: mutable variable `x` cannot be shadowed -/
#guard_msgs (error) in
example := (Id.run do let mut x := 42; x := x - 7; let x := x + 4; return x + 13)

/--
error: Application type mismatch: The argument
  true
has type
  Bool
but is expected to have type
  PUnit
in the application
  pure true
-/
#guard_msgs (error) in
example := (Id.run do pure true; pure ())

/--
error: Application type mismatch: The argument
  true
has type
  Bool
but is expected to have type
  PUnit
in the application
  pure true
---
error: Application type mismatch: The argument
  false
has type
  Bool
but is expected to have type
  PUnit
in the application
  pure false
-/
#guard_msgs (error) in
example := (Id.run do if true then {pure true} else {pure false}; pure ())

/--
error: Application type mismatch: The argument
  false
has type
  Bool
but is expected to have type
  PUnit
in the application
  pure false
-/
#guard_msgs (error) in
example := (Id.run do if true then {pure ()} else {pure false}; pure ())

-- Additional error tests

/--
error: Variable `foo` cannot be mutated. Only variables declared using `let mut` can be mutated.
      If you did not intend to mutate but define `foo`, consider using `let foo` instead
-/
#guard_msgs (error) in
example := (Id.run do foo := 42; pure ())

/-- error: mutable variable `x` cannot be shadowed -/
#guard_msgs (error) in
example := (Id.run do let mut x := 1; if true then {let mut x := 2; pure ()} else {pure ()}; pure x)

-- Test that we inline join points if they are just used once
set_option trace.Elab.do true in
/--
trace: [Elab.do] let x := 42;
    do
    let __s ←
      forIn [1, 2, 3] (none, x) fun i __s =>
          let x := __s.snd;
          if x = 3 then pure (ForInStep.done (some x, x))
          else
            if x > 10 then
              let x := x + 3;
              pure (ForInStep.yield (none, x))
            else
              if x < 20 then
                let x := x * 2;
                pure (ForInStep.done (none, x))
              else
                let x := x + i;
                pure (ForInStep.yield (none, x))
    let __r : Option ?m.170 := __s.fst
    let x : ?m.170 := __s.snd
    match __r with
      | some r => pure r
      | none =>
        let x := x + 13;
        let x := x + 13;
        let x := x + 13;
        let x := x + 13;
        pure x
-/
#guard_msgs in
example := Id.run do
  let mut x := 42
  for i in [1,2,3] do
    if x = 3 then return x
    if x > 10 then x := x + 3; continue
    if x < 20 then x := x * 2; break
    x := x + i
  x := x + 13
  x := x + 13
  x := x + 13
  x := x + 13
  return x

-- Test that we pass around all mutable variables that have been reassigned
/--
info: (let w := 23;
  let x := 42;
  let y := 0;
  let z := 1;
  do
  let __s ←
    forIn [1, 2, 3] (x, y, z) fun i __s =>
        let x := __s.fst;
        let __s_1 := __s.snd;
        let y := __s_1.fst;
        let z := __s_1.snd;
        if x < 20 then
          let y := y - 2;
          pure (ForInStep.done (x, y, z))
        else
          have __do_jp := fun __r z =>
            if x > 10 then
              let x := x + 3;
              pure (ForInStep.yield (x, y, z))
            else
              let x := x + i;
              pure (ForInStep.yield (x, y, z));
          if x = 3 then
            let z := z + i;
            __do_jp PUnit.unit z
          else __do_jp PUnit.unit z
  let x : Nat := __s.fst
  let __s : Nat × Nat := __s.snd
  let y : Nat := __s.fst
  let z : Nat := __s.snd
  pure (w + x + y + z)).run : Nat
-/
#guard_msgs (info) in
#check Id.run do
  let mut w := 23
  let mut x := 42
  let mut y := 0
  let mut z := 1
  for i in [1,2,3] do
    if x < 20 then y := y - 2; break
    if x = 3 then z := z + i
    if x > 10 then x := x + 3; continue
    x := x + i
  return w + x + y + z

namespace Repros

example {α} (mask : Array Bool) (xs : Array α) : Array α := Id.run do
  let mut ys := #[]
  for b in mask, x in xs do
    if b then ys := ys.push x
  return ys

-- Extracted from Lake.Build.Run. Tests if postponement and coercion insertion.
example : StateM (Nat × String) Unit := do
  let resetCtrl ← modifyGet fun s => (s.fst, {s with snd := ""})
  if resetCtrl.isValidChar then
    pure ()

structure AppImplicitArg where s : Term
def AppImplicitArg.syntax? (arg : AppImplicitArg) : Option Syntax := some arg.s

-- Extracted from Lean.PrettyPrinter.Delab.Builtins. Tests the interaction between `match` elaboration
-- and default instances.
example (fnStx : Syntax) (args : Array AppImplicitArg) : Option Syntax := do
  let x ← pure (f := Option) none <|> pure (f := Option) none
  match x with
  | none => have args : Array Syntax := args.filterMap (·.syntax?); return fnStx
  | some stx => return stx

-- Extracted from Lean.Language.Util. Tests that we try elaborating the join point RHS when
-- elaboration of the match fails
open Lean Language SnapshotTask in
partial example (s : SnapshotTree) : CoreM Unit :=
  go .skip s
where go range? s := do
  match range? with
  | .some _ => pure ()
  | .inherit => pure ()
  | .skip => pure ()
  s.children.toList.forM fun c => go c.reportingRange c.get

-- Extracted from Lean.Compiler.LCNF.Specialize.lean. Tests that we default when elaborating the
-- return argument.
example (paramsInfo : Array Nat) (args : Array Nat) : Array Nat := Id.run do
  let mut result := #[]
  for info in paramsInfo, arg in args do
    result := result.push arg
  pure <| result ++ args[paramsInfo.size...*]

-- Extracted from Lake.Config.Artifact. Tests that we allow pending instance resolution problems in
-- match discriminants which can be resolved when elaborating the match RHS.
example (data : Json) (k : String → Except String α) : Except String α := do
  match fromJson? data with
  | .ok out => k out
  | .error e => throw s!"artifact in unexpected JSON format: {e}"

-- Reproducer from Paul for testing that we elaborate the ρ synthesizing default instances.
example : Id Unit := do
  for x in 1...5 do
    pure ()
  return

example (tf : Float) (qf? : Option Float) : Id Unit := do
  if match qf? with | none => true | some qf => tf < qf then
    pure ()

-- This test ensures that `doLetElse` does not interpret the structure as a `doSeqBracketed`.
example (x : Nat) : Id (Bool × Bool) := do
  let 42 := x | { fst := true, snd := false } |> pure
  { fst := true, snd := false } |> pure

-- Test that `_ ← e` is allowed to discard results. (Implication: Can't expand to `x ← e`).
example (x : Nat) : Id Nat := do
  _ ← pure true
  return 0

-- Test that we can elaborate match RHS without knowing the continuation's result type (`MVarId`).
example (subgoals : List (Option Expr × MVarId)) : MetaM Unit :=
  discard <| subgoals.mapM fun ⟨e, mv⟩ ↦ do
    let mvar' ← match e with | none => pure mv | some _ => pure mv
    mvar'.withContext <| mvar'.assign Nat.mkType

-- test case doLetElse. We reject this because we don't support dependent match.
/--
error: Type mismatch
  y
has type
  Fin 3
but is expected to have type
  Fin (x + 1)
-/
#guard_msgs (error) in
example (x : Nat) : IO (Fin (x + 1)) := do
  let 2 := x | return 0
  -- the pattern match performed a substitution
  let y : Fin 3 := ⟨1, by decide⟩
  return y

-- Test new semantics of `let pat := rhs | otherwise`:
/-- info: "ok" -/
#guard_msgs in
#eval Id.run do
  let mut x := 0
  let y <- do
    let true := false | do x := x + 3; pure 0
    x := x + 100
    return "unreachable"
  if x + y < 23 then pure "ok" else pure "wrong"

-- extracted from grind_field_panic.lean
example (provided : Expr) : MetaM Expr := do
  let some className ← isClass? provided | failure
  let args : Array Expr := #[]
  let args ← args.mapM fun arg => do
      arg.fvarId!.getUserName
  return provided

/--
error: Application type mismatch: The argument
  h1
has type
  ¬iter1✝.IsAtEnd
but is expected to have type
  iter1 ≠ str1.endPos
in the application
  iter1.next h1
-/
#guard_msgs(error) in
example (str1 str2 : String) : Unit := Id.run do
  let mut iter1 := str1.startPos
  while h1 : ¬iter1.IsAtEnd do
    let mut iter2 := str2.startPos
    while h2 : ¬iter2.IsAtEnd do
      iter2 := iter2.next h2
      iter1 := iter1.next h1

example (str1 str2 : String) : Unit := Id.run do
  let mut iter1 := str1.startPos
  while h1 : ¬iter1.IsAtEnd do
    let mut iter2 := str2.startPos
    while h2 : ¬iter2.IsAtEnd do
      let cur := iter1.get h1
      iter2 := iter2.next h2
    iter1 := iter1.next h1

/-
The following example used to trigger a bug in `LocalContext.findFromUserNames` where the index
of the current declaration was tracked redundantly and incorrectly.
-/
def getZoneRules (id : String) : Except IO.Error Nat := do
  let mut start : Int := -2147483648
  let mut initialLocalTimeType : Nat := 0
  while true do
    let result : Option (Int × Int) ← pure (some (1, (2:Int)))
    if let some res := result then
      if res.fst ≤ start ∨ res.fst >= 32503690800 then
        break
      start := 0
    else
      break
  return initialLocalTimeType

example (toolchainFile : System.FilePath) : IO (Option Int) := do
  try
    let toolchainString ← IO.FS.readFile toolchainFile
    return some <| 42
  catch
    | .noFileOrDirectory .. =>
      return none
    | e => throw e

example (url : String) (headers : Array String := #[]) (thing : Except String Lake.JsonObject): IO Nat := do
  match thing with
  | .ok data =>
    match (data.get? "response_code" <|> data.get? "http_code") with
    | .ok (some code) => return code
    | _ => panic s!"curl's JSON output did not contain a response code"
  | .error e =>
    panic s!"curl produced invalid JSON output: {e}"

open Lean.Server in
example (handler : LspResponse respType → RequestM α)
    (r : SerializedLspResponse) (response : Json) [FromJson respType] : RequestM α := do
  let .ok response := fromJson? response
    | throw <| RequestError.internalError "Failed to convert response of previous request handler when chaining stateful LSP request handlers"
  let r := { r with response := response }
  handler r

example (url : String) (headers : Array String := #[]) (thing : Except String Lake.JsonObject): IO Nat :=
  match thing with
  | .ok data =>
    match (data.get? "response_code" <|> data.get? "http_code") with
    | .ok (some code) => return code
    | _ => panic s!"curl's JSON output did not contain a response code"
  | .error e =>
    panic s!"curl produced invalid JSON output: {e}"

example (cache : Std.HashMap (Nat × Nat) Bool) : Bool := Id.run do
  let key := ⟨1, 2⟩
  match cache[key]? with
  | some r => return true
  | none => pure ()
  let := cache.contains key
  return false

-- Extracted from MathlibTest.random.lean. The problem here is that the `match` elaborator used to
-- destructure the `(x, y)` pattern into `x` and `y` caused defaulting of `count` to `Nat`.
-- Nowadays, the `match` elaborator does not trigger global defaulting (in contrast to the term
-- `match` elaborator), fixing this test case.
example : IO Bool := do
  let mut count := 0
  for (x, y) in ([] : List (Nat × Nat)) do count := count + 1
  return Float.abs count > 0

-- A `repeat` (without `break`) followed by an early `return` inside an enclosing `for`. The for
-- elaborator computes the loop body's `ControlInfo` via `inferControlInfoSeq`, which stops
-- aggregating once it encounters an element with `numRegularExits := 0`. If `repeat` reported
-- `numRegularExits := 0` for break-less bodies, the trailing `return 2` would be skipped during
-- inference and the for elaborator would later throw "Early returning ... but the info said there
-- is no early return". This test pins down that `repeat` reports `numRegularExits := 1`, matching
-- `for ... in`.
example : IO Nat := do
  for _ in [1, 2] do
    repeat
      pure ()
    return 2
  return 1

end Repros
