diff --git a/Cabal/Cabal.cabal b/Cabal/Cabal.cabal
index 62ff7e99e42338d39e6118973508392a00258f03..8e9181bfbd5be4d08ed9999e0aa8df563dd949ed 100644
--- a/Cabal/Cabal.cabal
+++ b/Cabal/Cabal.cabal
@@ -59,6 +59,8 @@ extra-source-files:
   tests/ParserTests/errors/noVersion2.errors
   tests/ParserTests/errors/range-ge-wild.cabal
   tests/ParserTests/errors/range-ge-wild.errors
+  tests/ParserTests/errors/removed-fields.cabal
+  tests/ParserTests/errors/removed-fields.errors
   tests/ParserTests/errors/spdx-1.cabal
   tests/ParserTests/errors/spdx-1.errors
   tests/ParserTests/errors/spdx-2.cabal
diff --git a/Cabal/Distribution/FieldGrammar/Class.hs b/Cabal/Distribution/FieldGrammar/Class.hs
index 9400e04085f7d84804bd1d3e26a222fdcdfc43b2..ef895198e74f33845c8473bfccd1f6af4caf65bb 100644
--- a/Cabal/Distribution/FieldGrammar/Class.hs
+++ b/Cabal/Distribution/FieldGrammar/Class.hs
@@ -95,6 +95,13 @@ class FieldGrammar g where
         -> g s a
         -> g s a
 
+    -- | Removed in. If we occur removed field, parsing fails.
+    removedIn
+        :: CabalSpecVersion   -- ^ version
+        -> String             -- ^ removal message
+        -> g s a
+        -> g s a
+
     -- | Annotate field with since spec-version.
     availableSince
         :: CabalSpecVersion  -- ^ spec version
diff --git a/Cabal/Distribution/FieldGrammar/FieldDescrs.hs b/Cabal/Distribution/FieldGrammar/FieldDescrs.hs
index 4bd50d4604b020c025dc32c5106056763e2cd32f..e1e44a20898f7db48f6469ed85a0729e05190a77 100644
--- a/Cabal/Distribution/FieldGrammar/FieldDescrs.hs
+++ b/Cabal/Distribution/FieldGrammar/FieldDescrs.hs
@@ -82,5 +82,6 @@ instance FieldGrammar FieldDescrs where
     prefixedFields _fnPfx _l = F mempty
     knownField _           = pure ()
     deprecatedSince _  _ x = x
+    removedIn _ _ x        = x
     availableSince _ _     = id
     hiddenField _          = F mempty
diff --git a/Cabal/Distribution/FieldGrammar/Parsec.hs b/Cabal/Distribution/FieldGrammar/Parsec.hs
index 7ec44690ae4e7a16483bca92f52749c91b8b1a79..c6834e81f8c5277a29c2bc916f7f388bf62659c2 100644
--- a/Cabal/Distribution/FieldGrammar/Parsec.hs
+++ b/Cabal/Distribution/FieldGrammar/Parsec.hs
@@ -72,8 +72,8 @@ import Distribution.Simple.Utils   (fromUTF8BS)
 import Prelude ()
 
 import qualified Data.ByteString   as BS
-import qualified Data.Set          as Set
 import qualified Data.Map.Strict   as Map
+import qualified Data.Set          as Set
 import qualified Text.Parsec       as P
 import qualified Text.Parsec.Error as P
 
@@ -253,8 +253,33 @@ instance FieldGrammar ParsecFieldGrammar where
                             "The field " <> show name <> " is deprecated in the Cabal specification version " ++ showCabalSpecVersion vs ++ ". " ++ msg
 
                 parser v values
+
             | otherwise = parser v values
 
+    removedIn vs msg (ParsecFG names prefixes parser) = ParsecFG names prefixes parser' where
+        parser' v values
+            | v >= vs = do
+                let msg' = if null msg then "" else ' ' : msg
+                let unknownFields = Map.intersection values $ Map.fromSet (const ()) names
+                let namePos =
+                      [ (name, pos)
+                      | (name, fields) <- Map.toList unknownFields
+                      , MkNamelessField pos _ <- fields
+                      ]
+
+                let makeMsg name = "The field " <> show name <> " is removed in the Cabal specification version " ++ showCabalSpecVersion vs ++ "." ++ msg'
+
+                case namePos of
+                    -- no fields => proceed (with empty values, to be sure)
+                    [] -> parser v mempty
+
+                    -- if there's single field: fail fatally with it
+                    ((name, pos) : rest) -> do
+                        for_ rest $ \(name', pos') -> parseFailure pos' $ makeMsg name'
+                        parseFatalFailure pos $ makeMsg name
+
+              | otherwise = parser v values
+
     knownField fn = ParsecFG (Set.singleton fn) Set.empty (\_ _ -> pure ())
 
     hiddenField = id
diff --git a/Cabal/Distribution/FieldGrammar/Pretty.hs b/Cabal/Distribution/FieldGrammar/Pretty.hs
index d6180f738978456f28e905313897ebb75edd93bc..894fb3628743ce35f220e4a7580a289ac8bb9bfc 100644
--- a/Cabal/Distribution/FieldGrammar/Pretty.hs
+++ b/Cabal/Distribution/FieldGrammar/Pretty.hs
@@ -75,7 +75,11 @@ instance FieldGrammar PrettyFieldGrammar where
             ]
 
     knownField _           = pure ()
-    deprecatedSince _  _ x = x
+    deprecatedSince _ _ x  = x
+    -- TODO: as PrettyFieldGrammar isn't aware of cabal-version: we output the field
+    -- this doesn't affect roundtrip as `removedIn` fields cannot be parsed
+    -- so invalid documents can be only manually constructed.
+    removedIn _ _ x        = x
     availableSince _ _     = id
     hiddenField _          = PrettyFG (\_ -> mempty)
 
diff --git a/Cabal/Distribution/PackageDescription/FieldGrammar.hs b/Cabal/Distribution/PackageDescription/FieldGrammar.hs
index 9fb1fc70b50688a1e02043b0313e3ad6dfa1aa73..f10ac16828b14f77a6645ee5d1f75c23b199fd2f 100644
--- a/Cabal/Distribution/PackageDescription/FieldGrammar.hs
+++ b/Cabal/Distribution/PackageDescription/FieldGrammar.hs
@@ -380,6 +380,8 @@ buildInfoFieldGrammar = BuildInfo
     <*> monoidalFieldAla "build-tools"          (alaList  CommaFSep)          L.buildTools
         ^^^ deprecatedSince CabalSpecV2_0
             "Please use 'build-tool-depends' field"
+        ^^^ removedIn CabalSpecV3_0
+            "Please use 'build-tool-depends' field."
     <*> monoidalFieldAla "build-tool-depends"   (alaList  CommaFSep)          L.buildToolDepends
         -- {- ^^^ availableSince [2,0] [] -}
         -- here, we explicitly want to recognise build-tool-depends for all Cabal files
@@ -414,6 +416,8 @@ buildInfoFieldGrammar = BuildInfo
     <*> monoidalFieldAla "extensions"           (alaList' FSep MQuoted)       L.oldExtensions
         ^^^ deprecatedSince CabalSpecV1_12
             "Please use 'default-extensions' or 'other-extensions' fields."
+        ^^^ removedIn CabalSpecV3_0
+            "Please use 'default-extensions' or 'other-extensions' fields."
     <*> monoidalFieldAla "extra-libraries"      (alaList' VCat Token)         L.extraLibs
     <*> monoidalFieldAla "extra-ghci-libraries" (alaList' VCat Token)         L.extraGHCiLibs
     <*> monoidalFieldAla "extra-bundled-libraries" (alaList' VCat Token)      L.extraBundledLibs
@@ -443,6 +447,7 @@ hsSourceDirsGrammar = (++)
     <*> monoidalFieldAla "hs-source-dir"  (alaList' FSep FilePathNT) wrongLens
         --- https://github.com/haskell/cabal/commit/49e3cdae3bdf21b017ccd42e66670ca402e22b44
         ^^^ deprecatedSince CabalSpecV1_2 "Please use 'hs-source-dirs'"
+        ^^^ removedIn CabalSpecV3_0 "Please use 'hs-source-dirs' field."
   where
     -- TODO: make pretty printer aware of CabalSpecVersion
     wrongLens :: Functor f => LensLike' f BuildInfo [FilePath]
diff --git a/Cabal/doc/cabaldomain.py b/Cabal/doc/cabaldomain.py
index fd2f1846bd207dce65a59d495ca282f92315346b..d0cc9d3e17d783250662e51e2061b4e41b24b110 100644
--- a/Cabal/doc/cabaldomain.py
+++ b/Cabal/doc/cabaldomain.py
@@ -13,7 +13,10 @@ Most directives have at least following optional arguments
 
 `:deprecated: 1.24`
 `:deprecated:`
-    Feature was deprecatead, and optionally since which version.
+    Feature was deprecated, and optionally since which version.
+
+`:removed: 3.0`
+    Feature was removed
 
 `:synopsis: Short desc`
     Text used as short description on reference page.
@@ -164,12 +167,14 @@ class Meta(object):
     def __init__(self,
                  since=None,
                  deprecated=None,
+                 removed=None,
                  synopsis=None,
                  title=None,
                  section=None,
                  index=0):
         self.since = since
         self.deprecated = deprecated
+        self.removed = removed
         self.synopsis = synopsis
         self.title = title
         self.section = section
@@ -213,6 +218,7 @@ class CabalSection(Directive):
     option_spec = {
         'name': lambda x: x,
         'deprecated': parse_deprecated,
+        'removed': StrictVersion,
         'since' : StrictVersion,
         'synopsis' : lambda x:x,
     }
@@ -259,6 +265,7 @@ class CabalSection(Directive):
 
         meta = Meta(since=self.options.get('since'),
                     deprecated=self.options.get('deprecated'),
+                    removed=self.options.get('removed'),
                     synopsis=self.options.get('synopsis'),
                     index = num,
                     title = title)
@@ -274,6 +281,7 @@ class CabalObject(ObjectDescription):
     option_spec = {
         'noindex'   : directives.flag,
         'deprecated': parse_deprecated,
+        'removed'   : StrictVersion,
         'since'     : StrictVersion,
         'synopsis'  : lambda x:x
     }
@@ -299,6 +307,7 @@ class CabalObject(ObjectDescription):
         title = find_section_title(self.state.parent)
         return Meta(since=self.options.get('since'),
                     deprecated=self.options.get('deprecated'),
+                    removed=self.options.get('removed'),
                     title=title,
                     index = num,
                     synopsis=self.options.get('synopsis'))
@@ -409,6 +418,18 @@ class CabalObject(ObjectDescription):
                 field += field_body
                 field_list.insert(0, field)
 
+            if self.cabal_meta.removed is not None:
+                field = nodes.field('')
+                field_name = nodes.field_name('Removed', 'Removed')
+                if isinstance(self.cabal_meta.removed, StrictVersion):
+                    since = 'Cabal ' + str(self.cabal_meta.removed)
+                else:
+                    since = ''
+
+                field_body = nodes.field_body(since, nodes.paragraph(since, since))
+                field += field_name
+                field += field_body
+                field_list.insert(0, field)
         return result
 
 class CabalPackageSection(CabalObject):
@@ -459,6 +480,7 @@ class CabalField(CabalObject):
     option_spec = {
         'noindex'   : directives.flag,
         'deprecated': parse_deprecated,
+        'removed'   : StrictVersion,
         'since'     : StrictVersion,
         'synopsis'  : lambda x:x
     }
@@ -702,6 +724,11 @@ def render_deprecated(deprecated):
     else:
         return 'deprecated'
 
+def render_removed(deprecated, removed):
+    if isinstance(deprecated, StrictVersion):
+        return 'removed in: ' + str(removed) + '; deprecated since: '+str(deprecated)
+    else:
+        return 'removed in: ' + str(removed)
 
 def render_meta(meta):
     '''
@@ -709,6 +736,8 @@ def render_meta(meta):
 
     Will render either deprecated or since info
     '''
+    if meta.removed is not None:
+        return render_removed(meta.deprecated, meta.removed)
     if meta.deprecated is not None:
         return render_deprecated(meta.deprecated)
     elif meta.since is not None:
diff --git a/Cabal/doc/developing-packages.rst b/Cabal/doc/developing-packages.rst
index 3a2f65235015788fcb90e47c8ef155f4c9846f53..1ee81d94ea154eaf668c97c4850c4f5f49f80149 100644
--- a/Cabal/doc/developing-packages.rst
+++ b/Cabal/doc/developing-packages.rst
@@ -2173,7 +2173,8 @@ system-dependent values for these fields.
     :pkg-field:`other-extensions` declarations.
 
 .. pkg-field:: extensions: identifier list
-   :deprecated:
+   :deprecated: 1.12
+   :removed: 3.0
 
    Deprecated in favor of :pkg-field:`default-extensions`.
 
@@ -2230,7 +2231,8 @@ system-dependent values for these fields.
       compatibility.
 
 .. pkg-field:: build-tools: program list
-    :deprecated:
+    :deprecated: 2.0
+    :removed: 3.0
 
     Deprecated in favor of :pkg-field:`build-tool-depends`, but :ref:`see below for backwards compatibility information <buildtoolsbc>`.
 
diff --git a/Cabal/doc/file-format-changelog.rst b/Cabal/doc/file-format-changelog.rst
index e0745622917fdff3c40e2b2d53055c4a1ec0d00a..ef26050520af2bdb8988f4801c20f646eecd422a 100644
--- a/Cabal/doc/file-format-changelog.rst
+++ b/Cabal/doc/file-format-changelog.rst
@@ -22,13 +22,16 @@ relative to the respective preceding *published* version.
 ``cabal-version: 3.0``
 ----------------------
 
-* Added the `extra-dynamic-library-flavours` field to specify non-trivial
-  variants of dynamic flavours. It is `extra-library-flavours` but for
+* Added the :pkg-field:`extra-dynamic-library-flavours` field to specify non-trivial
+  variants of dynamic flavours. It is :pkg-field:`extra-library-flavours` but for
   shared libraries. Mainly useful for GHC's RTS library.
 
 * License fields use identifiers from SPDX License List version
   ``3.3 2018-10-24``
 
+* Remove deprecated ``hs-source-dir``, :pkg-field:`extensions` and
+  :pkg-field:`build-tools` fields.
+
 ``cabal-version: 2.4``
 ----------------------
 
diff --git a/Cabal/tests/ParserTests.hs b/Cabal/tests/ParserTests.hs
index c1df7d46e3eebd6da6d9d76131fb59a8fd2a16db..4a5a7430adea1c2d8e581732aac92bd3007570f8 100644
--- a/Cabal/tests/ParserTests.hs
+++ b/Cabal/tests/ParserTests.hs
@@ -106,6 +106,7 @@ errorTests = testGroup "errors"
     , errorTest "spdx-1.cabal"
     , errorTest "spdx-2.cabal"
     , errorTest "spdx-3.cabal"
+    , errorTest "removed-fields.cabal"
     ]
 
 errorTest :: FilePath -> TestTree
diff --git a/Cabal/tests/ParserTests/errors/removed-fields.cabal b/Cabal/tests/ParserTests/errors/removed-fields.cabal
new file mode 100644
index 0000000000000000000000000000000000000000..dbee53f75444718f8c8d33d37f435f47ec41aa5f
--- /dev/null
+++ b/Cabal/tests/ParserTests/errors/removed-fields.cabal
@@ -0,0 +1,14 @@
+cabal-version:       2.5
+name:                removed-fields
+version:             0
+synopsis:            some fields are removed
+build-type:          Simple
+
+library
+  default-language: Haskell2010
+  exposed-modules:  RemovedFields
+
+  build-depends: base, containers
+
+  extensions: CPP
+  extensions: DeriveFunctor
diff --git a/Cabal/tests/ParserTests/errors/removed-fields.errors b/Cabal/tests/ParserTests/errors/removed-fields.errors
new file mode 100644
index 0000000000000000000000000000000000000000..e41a0aee8266050b380668d68e94251e6a9536ed
--- /dev/null
+++ b/Cabal/tests/ParserTests/errors/removed-fields.errors
@@ -0,0 +1,3 @@
+VERSION: Just (mkVersion [2,5])
+removed-fields.cabal:13:3: The field "extensions" is removed in the Cabal specification version 3.0. Please use 'default-extensions' or 'other-extensions' fields.
+removed-fields.cabal:14:3: The field "extensions" is removed in the Cabal specification version 3.0. Please use 'default-extensions' or 'other-extensions' fields.