checkDataCon :: (?globals :: Globals)
  => Id -- ^ The type constructor and associated type to check against
  -> Kind -- ^ The kind of the type constructor
  -> Ctxt Kind -- ^ The type variables
  -> DataConstr -- ^ The data constructor to check
  -> Checker () -- ^ Return @Just ()@ on success, @Nothing@ on failure
checkDataCon
  tName
  kind
  tyVarsT
  d@(DataConstrIndexed sp dName tySch@(Forall s tyVarsD constraints ty)) = do
    case map fst $ intersectCtxts tyVarsT tyVarsD of
      [] -> do -- no clashes

        -- Only relevant type variables get included
        let tyVars = relevantSubCtxt (freeVars ty) (tyVarsT <> tyVarsD)
        let tyVars_justD = relevantSubCtxt (freeVars ty) tyVarsD

        -- Add the type variables from the data constructor into the environment
        modify $ \st -> st { tyVarContext =
               [(v, (k, ForallQ)) | (v, k) <- tyVars_justD] ++ tyVarContext st }
        tySchKind <- inferKindOfTypeInContext sp tyVars ty

        -- Freshen the data type constructors type
        (ty, tyVarsFreshD, _, constraints, []) <-
             freshPolymorphicInstance ForallQ False (Forall s tyVars constraints ty) []

        -- Create a version of the data constructor that matches the data type head
        -- but with a list of coercions

        (ty', coercions, tyVarsNewAndOld) <- checkAndGenerateSubstitution sp tName ty (indexKinds kind)

        -- Reconstruct the data constructor's new type scheme
        let tyVarsD' = tyVarsFreshD <> tyVarsNewAndOld
        let tySch = Forall sp tyVarsD' constraints ty'

        case tySchKind of
          KType ->
            registerDataConstructor tySch coercions

          KPromote (TyCon k) | internalName k == "Protocol" ->
            registerDataConstructor tySch coercions

          _ -> throw KindMismatch{ errLoc = sp, tyActualK = Just ty, kExpected = KType, kActual = kind }

      (v:vs) -> (throwError . fmap mkTyVarNameClashErr) (v:|vs)
  where
    indexKinds (KFun k1 k2) = k1 : indexKinds k2
    indexKinds k = []

    registerDataConstructor dataConstrTy subst = do
      st <- get
      case extend (dataConstructors st) dName (dataConstrTy, subst) of
        Just ds -> put st { dataConstructors = ds, tyVarContext = [] }
        Nothing -> throw DataConstructorNameClashError{ errLoc = sp, errId = dName }

    mkTyVarNameClashErr v = DataConstructorTypeVariableNameClash
        { errLoc = sp
        , errDataConstructorId = dName
        , errTypeConstructor = tName
        , errVar = v
        }
checkDataCon :: (?globals :: Globals)
  => Id -- ^ The type constructor and associated type to check against
  -> Kind -- ^ The kind of the type constructor
  -> Ctxt Kind -- ^ The type variables
  -> DataConstr -- ^ The data constructor to check
  -> Checker ()
checkDataCon
  tName
  kind
  tyVarsT
  d@(DataConstrIndexed sp dName tySch@(Forall s tyVarsD constraints ty)) = do
    case map fst $ intersectCtxts tyVarsT tyVarsD of
      [] -> do -- no clashes

        -- Only relevant type variables get included
        let tyVars = relevantSubCtxt (freeVars ty) (tyVarsT <> tyVarsD)
        let tyVars_justD = relevantSubCtxt (freeVars ty) tyVarsD

        -- Add the type variables from the data constructor into the environment
        modify $ \st -> st { tyVarContext =
               [(v, (k, ForallQ)) | (v, k) <- tyVars_justD] ++ tyVarContext st }
        tySchKind <- inferKindOfTypeInContext sp tyVars ty

        -- Freshen the data type constructors type
        (ty, tyVarsFreshD, _, constraints, []) <-
             freshPolymorphicInstance ForallQ False (Forall s tyVars constraints ty) []

        -- Create a version of the data constructor that matches the data type head
        -- but with a list of coercions

        (ty', coercions, tyVarsNewAndOld) <- checkAndGenerateSubstitution sp tName ty (indexKinds kind)

        -- Reconstruct the data constructor's new type scheme
        let tyVarsD' = tyVarsFreshD <> tyVarsNewAndOld
        let tySch = Forall sp tyVarsD' constraints ty'

        case tySchKind of
          KType ->
            registerDataConstructor tySch coercions

          KPromote (TyCon k) | internalName k == "Protocol" ->
            registerDataConstructor tySch coercions

          _ -> throw KindMismatch{ errLoc = sp, tyActualK = Just ty, kExpected = KType, kActual = kind }

      (v:vs) -> (throwError . fmap mkTyVarNameClashErr) (v:|vs)
  where
    indexKinds (KFun k1 k2) = k1 : indexKinds k2
    indexKinds k = []

    registerDataConstructor dataConstrTy subst = do
      st <- get
      case extend (dataConstructors st) dName (dataConstrTy, subst) of
        Just ds -> put st { dataConstructors = ds, tyVarContext = [] }
        Nothing -> throw DataConstructorNameClashError{ errLoc = sp, errId = dName }

    mkTyVarNameClashErr v = DataConstructorTypeVariableNameClash
        { errLoc = sp
        , errDataConstructorId = dName
        , errTypeConstructor = tName
        , errVar = v
        }
checkDataCon
  tName
  kind
  tyVarsT
  d@(DataConstrIndexed sp dName tySch@(Forall s tyVarsD constraints ty)) = do
    case map fst $ intersectCtxts tyVarsT tyVarsD of
      [] -> do -- no clashes

        -- Only relevant type variables get included
        let tyVars = relevantSubCtxt (freeVars ty) (tyVarsT <> tyVarsD)
        let tyVars_justD = relevantSubCtxt (freeVars ty) tyVarsD

        -- Add the type variables from the data constructor into the environment
        modify $ \st -> st { tyVarContext =
               [(v, (k, ForallQ)) | (v, k) <- tyVars_justD] ++ tyVarContext st }
        tySchKind <- inferKindOfTypeInContext sp tyVars ty

        -- Freshen the data type constructors type
        (ty, tyVarsFreshD, _, constraints, []) <-
             freshPolymorphicInstance ForallQ False (Forall s tyVars constraints ty) []

        -- Create a version of the data constructor that matches the data type head
        -- but with a list of coercions

        (ty', coercions, tyVarsNewAndOld) <- checkAndGenerateSubstitution sp tName ty (indexKinds kind)

        -- Reconstruct the data constructor's new type scheme
        let tyVarsD' = tyVarsFreshD <> tyVarsNewAndOld
        let tySch = Forall sp tyVarsD' constraints ty'

        case tySchKind of
          KType ->
            registerDataConstructor tySch coercions

          KPromote (TyCon k) | internalName k == "Protocol" ->
            registerDataConstructor tySch coercions

          _ -> throw KindMismatch{ errLoc = sp, tyActualK = Just ty, kExpected = KType, kActual = kind }

      (v:vs) -> (throwError . fmap mkTyVarNameClashErr) (v:|vs)
  where
    indexKinds (KFun k1 k2) = k1 : indexKinds k2
    indexKinds k = []

    registerDataConstructor dataConstrTy subst = do
      st <- get
      case extend (dataConstructors st) dName (dataConstrTy, subst) of
        Just ds -> put st { dataConstructors = ds, tyVarContext = [] }
        Nothing -> throw DataConstructorNameClashError{ errLoc = sp, errId = dName }

    mkTyVarNameClashErr v = DataConstructorTypeVariableNameClash
        { errLoc = sp
        , errDataConstructorId = dName
        , errTypeConstructor = tName
        , errVar = v
        }