synthExpr defs gam pol (Case s _ guardExpr cases) = do
  -- Synthesise the type of the guardExpr
  (guardTy, guardGam, substG, elaboratedGuard) <- synthExpr defs gam pol guardExpr
  -- then synthesise the types of the branches

  -- Dependent / GADT pattern matches not allowed in a case
  ixed <- isIndexedType guardTy
  when ixed (throw $ CaseOnIndexedType s guardTy)

  newCaseFrame

  branchTysAndCtxtsAndSubsts <-
    forM cases $ \(pati, ei) -> do
      -- Build the binding context for the branch pattern
      newConjunct
      (patternGam, eVars, subst, elaborated_pat_i, _) <- ctxtFromTypedPattern s guardTy pati NotFull
      newConjunct

      -- Synth the case body
      (tyCase, localGam, subst', elaborated_i) <- synthExpr defs (patternGam <> gam) pol ei

      -- Check that the use of locally bound variables matches their bound type
      ctxtApprox s (localGam `intersectCtxts` patternGam) patternGam

      -- Conclude
      concludeImplication (getSpan pati) eVars

      -- Check linear use in this branch
      gamSoFar <- ctxtPlus s guardGam localGam
      case checkLinearity patternGam gamSoFar of
         -- Return the resulting computed context, without any of
         -- the variable bound in the pattern of this branch
         [] -> return (tyCase
                    , (localGam `subtractCtxt` patternGam, subst')
                    , (elaborated_pat_i, elaborated_i))
         p:ps -> illLinearityMismatch s (p:|ps)

  -- All branches must be possible
  checkGuardsForImpossibility s $ mkId "case"

  popCaseFrame

  let (branchTys, branchCtxtsAndSubsts, elaboratedCases) = unzip3 branchTysAndCtxtsAndSubsts
  let (branchCtxts, branchSubsts) = unzip branchCtxtsAndSubsts
  let branchTysAndSpans = zip branchTys (map (getSpan . snd) cases)

  -- Finds the upper-bound return type between all branches
  branchType <- foldM (\ty2 (ty1, sp) -> joinTypes sp ty1 ty2)
                   (head branchTys)
                   (tail branchTysAndSpans)

  -- Find the upper-bound type on the return contexts
  (branchesGam, tyVars) <- foldM (\(ctxt, vars) ctxt' -> do
    (ctxt'', vars') <- joinCtxts s ctxt ctxt'
    return (ctxt'', vars ++ vars')) (head branchCtxts, []) (tail branchCtxts)

  -- Contract the outgoing context of the guard and the branches (joined)
  gamNew <- ctxtPlus s branchesGam guardGam

  subst <- combineManySubstitutions s (substG : branchSubsts)

  -- Exisentially quantify any ty variables generated by joining contexts
  mapM_ (uncurry existential) tyVars

  let elaborated = Case s branchType elaboratedGuard elaboratedCases
  return (branchType, gamNew, subst, elaborated)

-- Diamond cut
-- let [[p]] <- [[e1 : sig]] in [[e2 : tau]]
synthExpr defs gam pol (Case s _ guardExpr cases) = do
  -- Synthesise the type of the guardExpr
  (guardTy, guardGam, substG, elaboratedGuard) <- synthExpr defs gam pol guardExpr
  -- then synthesise the types of the branches

  -- Dependent / GADT pattern matches not allowed in a case
  ixed <- isIndexedType guardTy
  when ixed (throw $ CaseOnIndexedType s guardTy)

  newCaseFrame

  branchTysAndCtxtsAndSubsts <-
    forM cases $ \(pati, ei) -> do
      -- Build the binding context for the branch pattern
      newConjunct
      (patternGam, eVars, subst, elaborated_pat_i, _) <- ctxtFromTypedPattern s guardTy pati NotFull
      newConjunct

      -- Synth the case body
      (tyCase, localGam, subst', elaborated_i) <- synthExpr defs (patternGam <> gam) pol ei

      -- Check that the use of locally bound variables matches their bound type
      ctxtApprox s (localGam `intersectCtxts` patternGam) patternGam

      -- Conclude
      concludeImplication (getSpan pati) eVars

      -- Check linear use in this branch
      gamSoFar <- ctxtPlus s guardGam localGam
      case checkLinearity patternGam gamSoFar of
         -- Return the resulting computed context, without any of
         -- the variable bound in the pattern of this branch
         [] -> return (tyCase
                    , (localGam `subtractCtxt` patternGam, subst')
                    , (elaborated_pat_i, elaborated_i))
         p:ps -> illLinearityMismatch s (p:|ps)

  -- All branches must be possible
  checkGuardsForImpossibility s $ mkId "case"

  popCaseFrame

  let (branchTys, branchCtxtsAndSubsts, elaboratedCases) = unzip3 branchTysAndCtxtsAndSubsts
  let (branchCtxts, branchSubsts) = unzip branchCtxtsAndSubsts
  let branchTysAndSpans = zip branchTys (map (getSpan . snd) cases)

  -- Finds the upper-bound return type between all branches
  branchType <- foldM (\ty2 (ty1, sp) -> joinTypes sp ty1 ty2)
                   (head branchTys)
                   (tail branchTysAndSpans)

  -- Find the upper-bound type on the return contexts
  (branchesGam, tyVars) <- foldM (\(ctxt, vars) ctxt' -> do
    (ctxt'', vars') <- joinCtxts s ctxt ctxt'
    return (ctxt'', vars ++ vars')) (head branchCtxts, []) (tail branchCtxts)

  -- Contract the outgoing context of the guard and the branches (joined)
  gamNew <- ctxtPlus s branchesGam guardGam

  subst <- combineManySubstitutions s (substG : branchSubsts)

  -- Exisentially quantify any ty variables generated by joining contexts
  mapM_ (uncurry existential) tyVars

  let elaborated = Case s branchType elaboratedGuard elaboratedCases
  return (branchType, gamNew, subst, elaborated)

-- Diamond cut
-- let [[p]] <- [[e1 : sig]] in [[e2 : tau]]
synthExpr defs gam pol (Case s _ guardExpr cases) = do
  -- Synthesise the type of the guardExpr
  (guardTy, guardGam, substG, elaboratedGuard) <- synthExpr defs gam pol guardExpr
  -- then synthesise the types of the branches

  -- Dependent / GADT pattern matches not allowed in a case
  ixed <- isIndexedType guardTy
  when ixed (throw $ CaseOnIndexedType s guardTy)

  newCaseFrame

  branchTysAndCtxtsAndSubsts <-
    forM cases $ \(pati, ei) -> do
      -- Build the binding context for the branch pattern
      newConjunct
      (patternGam, eVars, subst, elaborated_pat_i, _) <- ctxtFromTypedPattern s guardTy pati NotFull
      newConjunct

      -- Synth the case body
      (tyCase, localGam, subst', elaborated_i) <- synthExpr defs (patternGam <> gam) pol ei

      -- Check that the use of locally bound variables matches their bound type
      ctxtApprox s (localGam `intersectCtxts` patternGam) patternGam

      -- Conclude
      concludeImplication (getSpan pati) eVars

      -- Check linear use in this branch
      gamSoFar <- ctxtPlus s guardGam localGam
      case checkLinearity patternGam gamSoFar of
         -- Return the resulting computed context, without any of
         -- the variable bound in the pattern of this branch
         [] -> return (tyCase
                    , (localGam `subtractCtxt` patternGam, subst')
                    , (elaborated_pat_i, elaborated_i))
         p:ps -> illLinearityMismatch s (p:|ps)

  -- All branches must be possible
  checkGuardsForImpossibility s $ mkId "case"

  popCaseFrame

  let (branchTys, branchCtxtsAndSubsts, elaboratedCases) = unzip3 branchTysAndCtxtsAndSubsts
  let (branchCtxts, branchSubsts) = unzip branchCtxtsAndSubsts
  let branchTysAndSpans = zip branchTys (map (getSpan . snd) cases)

  -- Finds the upper-bound return type between all branches
  branchType <- foldM (\ty2 (ty1, sp) -> joinTypes sp ty1 ty2)
                   (head branchTys)
                   (tail branchTysAndSpans)

  -- Find the upper-bound type on the return contexts
  (branchesGam, tyVars) <- foldM (\(ctxt, vars) ctxt' -> do
    (ctxt'', vars') <- joinCtxts s ctxt ctxt'
    return (ctxt'', vars ++ vars')) (head branchCtxts, []) (tail branchCtxts)

  -- Contract the outgoing context of the guard and the branches (joined)
  gamNew <- ctxtPlus s branchesGam guardGam

  subst <- combineManySubstitutions s (substG : branchSubsts)

  -- Exisentially quantify any ty variables generated by joining contexts
  mapM_ (uncurry existential) tyVars

  let elaborated = Case s branchType elaboratedGuard elaboratedCases
  return (branchType, gamNew, subst, elaborated)

-- Diamond cut
-- let [[p]] <- [[e1 : sig]] in [[e2 : tau]]