diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,20 @@
 
 ## [Unreleased]
 
+## [5.0.1.0] - 2026-10-21
+
+### Added
+
+- `mkSpanAnnotation :: SourceSpan -> Annotation` makes a span annotation
+  without the run-time type test of `mkAnnotation`.
+
+### Performance
+
+- The parser uses `mkSpanAnnotation` for all its span annotations. Before,
+  each annotation was a thunk that kept its whole token alive until something
+  forced it. On the aihc-base benchmark, the peak heap is 15% smaller and GC
+  copies 5% fewer bytes.
+
 ## [5.0.0.0] - 2026-09-30
 
 ### Added
diff --git a/aihc-parser.cabal b/aihc-parser.cabal
--- a/aihc-parser.cabal
+++ b/aihc-parser.cabal
@@ -1,6 +1,6 @@
 cabal-version: 3.8
 name: aihc-parser
-version: 5.0.0.0
+version: 5.0.1.0
 build-type: Simple
 license: Unlicense
 license-file: LICENSE
diff --git a/src/Aihc/Parser/Internal/Cmd.hs b/src/Aihc/Parser/Internal/Cmd.hs
--- a/src/Aihc/Parser/Internal/Cmd.hs
+++ b/src/Aihc/Parser/Internal/Cmd.hs
@@ -90,13 +90,13 @@
 
 -- | Parse a command do-block: @do { cstmt ; ... }@
 cmdDoParser :: TokParser Cmd
-cmdDoParser = withSpanAnn (CmdAnn . mkAnnotation) $ do
+cmdDoParser = withSpanAnn (CmdAnn . mkSpanAnnotation) $ do
   expectedTok TkKeywordDo
   CmdDo <$> bracedSemiSep1 cmdStmtParser
 
 -- | Parse a command if-then-else: @if exp then cmd else cmd@
 cmdIfParser :: TokParser Cmd
-cmdIfParser = withSpanAnn (CmdAnn . mkAnnotation) $ do
+cmdIfParser = withSpanAnn (CmdAnn . mkSpanAnnotation) $ do
   expectedTok TkKeywordIf
   cond <- exprParser
   skipSemicolons
@@ -108,7 +108,7 @@
 
 -- | Parse a command case: @case exp of { calts }@
 cmdCaseParser :: TokParser Cmd
-cmdCaseParser = withSpanAnn (CmdAnn . mkAnnotation) $ do
+cmdCaseParser = withSpanAnn (CmdAnn . mkSpanAnnotation) $ do
   expectedTok TkKeywordCase
   scrut <- region "while parsing case scrutinee" exprParser
   expectedTok TkKeywordOf
@@ -119,18 +119,18 @@
 cmdCaseAltParser = withSpan $ do
   pat <- caseAltPatternParser
   rhs <- caseRhsParserWithBodyParser cmdParser
-  pure (\span' -> CaseAlt [mkAnnotation span'] pat rhs)
+  pure (\span' -> CaseAlt [mkSpanAnnotation span'] pat rhs)
 
 -- | Parse a command let: @let decls in cmd@
 cmdLetParser :: TokParser Cmd
-cmdLetParser = withSpanAnn (CmdAnn . mkAnnotation) $ do
+cmdLetParser = withSpanAnn (CmdAnn . mkSpanAnnotation) $ do
   decls <- parseLetDeclsParser
   expectedTok TkKeywordIn
   CmdLet decls <$> cmdParser
 
 -- | Parse a command lambda: @\\pats -> cmd@
 cmdLamParser :: TokParser Cmd
-cmdLamParser = withSpanAnn (CmdAnn . mkAnnotation) $ do
+cmdLamParser = withSpanAnn (CmdAnn . mkSpanAnnotation) $ do
   expectedTok TkReservedBackslash
   pats <- MP.some apatParser
   expectedTok TkReservedRightArrow
@@ -139,7 +139,7 @@
 -- | Parse a parenthesised command: @( cmd )@
 cmdParenParser :: TokParser Cmd
 cmdParenParser =
-  withSpanAnn (CmdAnn . mkAnnotation) $
+  withSpanAnn (CmdAnn . mkSpanAnnotation) $
     CmdPar <$> parens cmdParser
 
 -- | Parse a do-statement in command context (arrow do).
@@ -171,7 +171,7 @@
 -- | Parse a command do-statement: @cmd@ or @pat <- cmd@.
 -- Uses the expression-first approach: parse as expression, check for @<-@.
 cmdBindOrBodyStmtParser :: TokParser (DoStmt Cmd)
-cmdBindOrBodyStmtParser = withSpanAnn (DoAnn . mkAnnotation) $ do
+cmdBindOrBodyStmtParser = withSpanAnn (DoAnn . mkSpanAnnotation) $ do
   -- Arrow tails (-<, -<<) belong to the command level, not the expression.
   expr <- exprParser
   hasArrow <- optionalTok TkReservedLeftArrow
@@ -195,7 +195,7 @@
 -- | Parse a command bind statement where the pattern is unambiguously a
 -- pattern (starts with !, ~, or x@).
 cmdBindStmtParser :: TokParser (DoStmt Cmd)
-cmdBindStmtParser = withSpanAnn (DoAnn . mkAnnotation) $ do
+cmdBindStmtParser = withSpanAnn (DoAnn . mkSpanAnnotation) $ do
   pat <- patternParser
   expectedTok TkReservedLeftArrow
   cmd <- region "while parsing '<-' binding" cmdParser
@@ -204,17 +204,17 @@
 -- | Parse a body-only command statement (fallback from cmdStmtParser).
 cmdBodyStmtParser :: TokParser (DoStmt Cmd)
 cmdBodyStmtParser =
-  withSpanAnn (DoAnn . mkAnnotation) $
+  withSpanAnn (DoAnn . mkSpanAnnotation) $
     DoExpr <$> cmdParser
 
 -- | Parse a command let-statement: @let decls@
 cmdLetStmtParser :: TokParser (DoStmt Cmd)
 cmdLetStmtParser =
-  withSpanAnn (DoAnn . mkAnnotation) $
+  withSpanAnn (DoAnn . mkSpanAnnotation) $
     DoLetDecls <$> parseLetDeclsStmtParser
 
 -- | Parse a command rec-statement: @rec { cstmts }@
 cmdRecStmtParser :: TokParser (DoStmt Cmd)
-cmdRecStmtParser = withSpanAnn (DoAnn . mkAnnotation) $ do
+cmdRecStmtParser = withSpanAnn (DoAnn . mkSpanAnnotation) $ do
   expectedTok TkKeywordRec
   DoRecStmt <$> bracedSemiSep1 cmdStmtParser
diff --git a/src/Aihc/Parser/Internal/Common.hs b/src/Aihc/Parser/Internal/Common.hs
--- a/src/Aihc/Parser/Internal/Common.hs
+++ b/src/Aihc/Parser/Internal/Common.hs
@@ -544,11 +544,11 @@
 
 mkUnqualifiedNameAt :: LexToken -> NameType -> Text -> UnqualifiedName
 mkUnqualifiedNameAt tok ty txt =
-  UnqualifiedName ty txt [mkAnnotation (lexTokenSpan tok)]
+  UnqualifiedName ty txt [mkSpanAnnotation (lexTokenSpan tok)]
 
 mkNameAt :: LexToken -> Maybe Text -> NameType -> Text -> Name
 mkNameAt tok qualifier ty txt =
-  Name qualifier ty txt [mkAnnotation (lexTokenSpan tok)]
+  Name qualifier ty txt [mkSpanAnnotation (lexTokenSpan tok)]
 
 nameToUnqualified :: Name -> UnqualifiedName
 nameToUnqualified name =
@@ -694,7 +694,7 @@
 
 contextItemParserWith :: TokParser Type -> TokParser Type -> TokParser Type
 contextItemParserWith typeParser typeAtomParser =
-  withSpanAnn (TAnn . mkAnnotation) $
+  withSpanAnn (TAnn . mkSpanAnnotation) $
     MP.try parenthesizedContextItemParser <|> MP.try kindSigContextItemParser <|> bareContextItemParser
   where
     bareContextItemParser =
@@ -1090,7 +1090,7 @@
   pure (thEnabled || thFullEnabled)
 
 asPatternParser :: TokParser Pattern -> TokParser Pattern
-asPatternParser bodyParser = withSpanAnn (PAnn . mkAnnotation) $ do
+asPatternParser bodyParser = withSpanAnn (PAnn . mkSpanAnnotation) $ do
   name <- MP.try (binderNameParser <* expectedTok TkReservedAt)
   PAs name <$> bodyParser
 
diff --git a/src/Aihc/Parser/Internal/Decl.hs b/src/Aihc/Parser/Internal/Decl.hs
--- a/src/Aihc/Parser/Internal/Decl.hs
+++ b/src/Aihc/Parser/Internal/Decl.hs
@@ -135,7 +135,7 @@
 -- detects that case early (after parsing the pattern) and fails, letting
 -- 'valueDeclParser' handle it instead.
 nonBareVarPatternBindDeclParser :: TokParser Decl
-nonBareVarPatternBindDeclParser = MP.try $ withSpanAnn (DeclAnn . mkAnnotation) $ do
+nonBareVarPatternBindDeclParser = MP.try $ withSpanAnn (DeclAnn . mkSpanAnnotation) $ do
   pat <- region "while parsing pattern binding" patternParser
   case pat of
     PVar {} -> fail "bare variable bindings are parsed as function declarations"
@@ -144,7 +144,7 @@
 
 -- | Parse a pragma declaration (e.g. {-# INLINE f #-}, {-# SPECIALIZE ... #-})
 pragmaDeclParser :: TokParser Decl
-pragmaDeclParser = withSpanAnn (DeclAnn . mkAnnotation) $ DeclPragma <$> anyPragmaParser "pragma declaration"
+pragmaDeclParser = withSpanAnn (DeclAnn . mkSpanAnnotation) $ DeclPragma <$> anyPragmaParser "pragma declaration"
 
 -- | Parse a @RULES@ pragma:
 --
@@ -155,7 +155,7 @@
 -- rules are separated by semicolons, which the layout rule supplies for a
 -- rule that starts in the column of the enclosing declarations.
 rulesDeclParser :: TokParser Decl
-rulesDeclParser = withSpanAnn (DeclAnn . mkAnnotation) $ do
+rulesDeclParser = withSpanAnn (DeclAnn . mkSpanAnnotation) $ do
   expectedTok (TkPragmaOpen "RULES")
   rules <- region "while parsing RULES pragma" (plainSemiSep ruleDeclParser)
   expectedTok TkPragmaClose
@@ -181,7 +181,7 @@
   rhs <- region "while parsing rule right-hand side" exprParser
   pure $ \span' ->
     RuleDecl
-      { ruleAnns = [mkAnnotation span'],
+      { ruleAnns = [mkSpanAnnotation span'],
         ruleName = name,
         ruleActivation = activation,
         ruleTypeBinders = typeBinders,
@@ -229,7 +229,7 @@
   withSpan $
     ( do
         name <- ruleBinderNameParser
-        pure (\span' -> RuleBinder [mkAnnotation span'] name Nothing)
+        pure (\span' -> RuleBinder [mkSpanAnnotation span'] name Nothing)
     )
       <|> ( do
               expectedTok TkSpecialLParen
@@ -237,7 +237,7 @@
               expectedTok TkReservedDoubleColon
               ty <- typeParser
               expectedTok TkSpecialRParen
-              pure (\span' -> RuleBinder [mkAnnotation span'] name (Just ty))
+              pure (\span' -> RuleBinder [mkSpanAnnotation span'] name (Just ty))
           )
 
 ruleBinderNameParser :: TokParser UnqualifiedName
@@ -276,7 +276,7 @@
 -- constructs (e.g. @$expr@, @$(expr)@ via TH, @[qq|...|]@ via QuasiQuotes),
 -- so no special dispatch is needed here.
 exprDeclParser :: TokParser Decl
-exprDeclParser = withSpanAnn (DeclAnn . mkAnnotation) $ DeclSplice <$> exprParser
+exprDeclParser = withSpanAnn (DeclAnn . mkSpanAnnotation) $ DeclSplice <$> exprParser
 
 -- | Parse a @type@ declaration after the @type@ keyword has been consumed.
 -- Uses 'typeDeclHeadParser' to handle both prefix and infix type heads,
@@ -286,7 +286,7 @@
 --
 -- > topdecl -> 'type' simpletype '=' type
 typeDeclarationParser :: TokParser Decl
-typeDeclarationParser = withSpanAnn (DeclAnn . mkAnnotation) $ do
+typeDeclarationParser = withSpanAnn (DeclAnn . mkSpanAnnotation) $ do
   expectedTok TkKeywordType
   typeHead <- typeDeclHeadParser
   nextTok <- anySingle
@@ -315,7 +315,7 @@
           }
 
 roleAnnotationDeclParser :: TokParser Decl
-roleAnnotationDeclParser = withSpanAnn (DeclAnn . mkAnnotation) $ do
+roleAnnotationDeclParser = withSpanAnn (DeclAnn . mkSpanAnnotation) $ do
   expectedTok TkKeywordType
   expectedTok TkVarRole
   typeName <- constructorUnqualifiedNameParser <|> parens constructorOperatorUnqualifiedNameParser
@@ -424,7 +424,7 @@
       withSpan $
         ( do
             ident <- lowerIdentifierParser
-            pure (\span' -> TyVarBinder [mkAnnotation span'] ident Nothing TyVarBSpecified TyVarBVisible)
+            pure (\span' -> TyVarBinder [mkSpanAnnotation span'] ident Nothing TyVarBSpecified TyVarBVisible)
         )
           <|> ( do
                   expectedTok TkSpecialLParen
@@ -432,7 +432,7 @@
                   expectedTok TkReservedDoubleColon
                   kind <- typeParser
                   expectedTok TkSpecialRParen
-                  pure (\span' -> TyVarBinder [mkAnnotation span'] ident (Just kind) TyVarBSpecified TyVarBVisible)
+                  pure (\span' -> TyVarBinder [mkSpanAnnotation span'] ident (Just kind) TyVarBSpecified TyVarBVisible)
               )
 
 typeFamilyInjectivityParser :: TokParser TypeFamilyInjectivity
@@ -443,7 +443,7 @@
   determined <- MP.some lowerIdentifierParser
   pure $ \span' ->
     TypeFamilyInjectivity
-      { typeFamilyInjectivityAnns = [mkAnnotation span'],
+      { typeFamilyInjectivityAnns = [mkSpanAnnotation span'],
         typeFamilyInjectivityResult = result,
         typeFamilyInjectivityDetermined = determined
       }
@@ -454,7 +454,7 @@
 -- | Parse @type family Name params [:: Kind] [where { equations }]@
 typeFamilyDeclParser :: TokParser Decl
 typeFamilyDeclParser =
-  withSpanAnn (DeclAnn . mkAnnotation) $
+  withSpanAnn (DeclAnn . mkSpanAnnotation) $
     DeclTypeFamilyDecl <$> typeFamilyDeclBodyParser FamilyKeywordRequired
 
 -- | Parse the @where { eq; ... }@ block of a closed type family.
@@ -470,7 +470,7 @@
   rhs <- typeParser
   pure $ \span' ->
     TypeFamilyEq
-      { typeFamilyEqAnns = [mkAnnotation span'],
+      { typeFamilyEqAnns = [mkSpanAnnotation span'],
         typeFamilyEqForall = forallBinders,
         typeFamilyEqHeadForm = headForm,
         typeFamilyEqLhs = lhs,
@@ -482,7 +482,7 @@
 
 -- | Parse @data family Name params [:: Kind]@
 dataFamilyDeclParser :: TokParser Decl
-dataFamilyDeclParser = withSpanAnn (DeclAnn . mkAnnotation) $ do
+dataFamilyDeclParser = withSpanAnn (DeclAnn . mkSpanAnnotation) $ do
   expectedTok TkKeywordData
   varIdTok "family"
   head' <- typeDeclHeadParser
@@ -499,7 +499,7 @@
 
 -- | Parse @type instance [forall binders.] LhsType = RhsType@
 typeFamilyInstParser :: TokParser Decl
-typeFamilyInstParser = withSpanAnn (DeclAnn . mkAnnotation) $ do
+typeFamilyInstParser = withSpanAnn (DeclAnn . mkSpanAnnotation) $ do
   expectedTok TkKeywordType
   expectedTok TkKeywordInstance
   forallBinders <- MP.option [] explicitForallParser
@@ -517,7 +517,7 @@
 
 -- | Parse @data instance [forall binders.] HeadType = Cons | ...@ (also GADT style)
 dataFamilyInstParser :: TokParser Decl
-dataFamilyInstParser = withSpanAnn (DeclAnn . mkAnnotation) $ do
+dataFamilyInstParser = withSpanAnn (DeclAnn . mkSpanAnnotation) $ do
   expectedTok TkKeywordData
   expectedTok TkKeywordInstance
   forallBinders <- MP.option [] explicitForallParser
@@ -537,7 +537,7 @@
 
 -- | Parse @newtype instance [forall binders.] HeadType = Constructor@
 newtypeFamilyInstParser :: TokParser Decl
-newtypeFamilyInstParser = withSpanAnn (DeclAnn . mkAnnotation) $ do
+newtypeFamilyInstParser = withSpanAnn (DeclAnn . mkSpanAnnotation) $ do
   expectedTok TkKeywordNewtype
   expectedTok TkKeywordInstance
   forallBinders <- MP.option [] explicitForallParser
@@ -565,12 +565,12 @@
 -- (which is handled by 'classDefaultTypeInstParser' via token dispatch).
 classTypeFamilyDeclParser :: TokParser ClassDeclItem
 classTypeFamilyDeclParser =
-  withSpanAnn (ClassItemAnn . mkAnnotation) $
+  withSpanAnn (ClassItemAnn . mkSpanAnnotation) $
     ClassItemTypeFamilyDecl <$> typeFamilyDeclBodyParser FamilyKeywordOptional
 
 -- | Parse @data Name params [:: Kind]@ as an associated data family in a class.
 classDataFamilyDeclParser :: TokParser ClassDeclItem
-classDataFamilyDeclParser = withSpanAnn (ClassItemAnn . mkAnnotation) $ do
+classDataFamilyDeclParser = withSpanAnn (ClassItemAnn . mkSpanAnnotation) $ do
   expectedTok TkKeywordData
   head' <- typeDeclHeadParser
   kind <- familyResultKindParser
@@ -592,7 +592,7 @@
 classDefaultTypeInstShorthandParser = classDefaultTypeInstParser' False
 
 classDefaultTypeInstParser' :: Bool -> TokParser ClassDeclItem
-classDefaultTypeInstParser' requireInstance = withSpanAnn (ClassItemAnn . mkAnnotation) $ do
+classDefaultTypeInstParser' requireInstance = withSpanAnn (ClassItemAnn . mkSpanAnnotation) $ do
   expectedTok TkKeywordType
   when requireInstance (expectedTok TkKeywordInstance)
   forallBinders <- MP.option [] explicitForallParser
@@ -616,7 +616,7 @@
 -- The @instance@ keyword is accepted but optional (GHC normalizes both forms
 -- to the same AST, so we treat them identically).
 instanceTypeFamilyInstParser :: TokParser InstanceDeclItem
-instanceTypeFamilyInstParser = withSpanAnn (InstanceItemAnn . mkAnnotation) $ do
+instanceTypeFamilyInstParser = withSpanAnn (InstanceItemAnn . mkSpanAnnotation) $ do
   expectedTok TkKeywordType
   _ <- optionalTok TkKeywordInstance
   forallBinders <- MP.option [] explicitForallParser
@@ -635,7 +635,7 @@
 -- | Parse @data [instance] HeadType = Cons | ...@ (or GADT style) inside an instance body.
 -- The @instance@ keyword is accepted but optional.
 instanceDataFamilyInstParser :: TokParser InstanceDeclItem
-instanceDataFamilyInstParser = withSpanAnn (InstanceItemAnn . mkAnnotation) $ do
+instanceDataFamilyInstParser = withSpanAnn (InstanceItemAnn . mkSpanAnnotation) $ do
   expectedTok TkKeywordData
   _ <- optionalTok TkKeywordInstance
   (_, head') <- typeFamilyLhsParser
@@ -655,7 +655,7 @@
 -- | Parse @newtype [instance] HeadType = Constructor@ inside an instance body.
 -- The @instance@ keyword is accepted but optional.
 instanceNewtypeFamilyInstParser :: TokParser InstanceDeclItem
-instanceNewtypeFamilyInstParser = withSpanAnn (InstanceItemAnn . mkAnnotation) $ do
+instanceNewtypeFamilyInstParser = withSpanAnn (InstanceItemAnn . mkSpanAnnotation) $ do
   expectedTok TkKeywordNewtype
   _ <- optionalTok TkKeywordInstance
   (_, head') <- typeFamilyLhsParser
@@ -681,7 +681,7 @@
 -- > gendecl -> vars '::' [context '=>'] type
 typeSigDeclParser :: TokParser Decl
 typeSigDeclParser =
-  withSpanAnn (DeclAnn . mkAnnotation) $
+  withSpanAnn (DeclAnn . mkSpanAnnotation) $
     uncurry DeclTypeSig <$> typedSignaturePrefixParser typeSignatureParser
 
 -- | Parse a type signature or a pattern-typed equation.
@@ -698,7 +698,7 @@
 -- is returned.
 typeSigOrPatternTypeSigDeclParser :: TokParser Decl
 typeSigOrPatternTypeSigDeclParser =
-  withSpanAnn (DeclAnn . mkAnnotation) $
+  withSpanAnn (DeclAnn . mkSpanAnnotation) $
     typedBindingOrSignatureParser
       typeSignatureParser
       DeclTypeSig
@@ -710,7 +710,7 @@
       "typed pattern bindings with '=' require exactly one binder"
 
 defaultDeclParser :: TokParser Decl
-defaultDeclParser = withSpanAnn (DeclAnn . mkAnnotation) $ do
+defaultDeclParser = withSpanAnn (DeclAnn . mkSpanAnnotation) $ do
   expectedTok TkKeywordDefault
   DeclDefault <$> parens (typeParser `MP.sepEndBy` expectedTok TkSpecialComma)
 
@@ -718,7 +718,7 @@
 --
 -- > gendecl -> fixity [integer] ops
 fixityDeclParser :: TokParser Decl
-fixityDeclParser = withSpanAnn (DeclAnn . mkAnnotation) $ do
+fixityDeclParser = withSpanAnn (DeclAnn . mkSpanAnnotation) $ do
   (parsedAssoc, prec, mNamespace, ops) <- fixityDeclPartsParser
   pure (DeclFixity parsedAssoc mNamespace prec ops)
 
@@ -771,7 +771,7 @@
 --
 -- > topdecl -> 'class' [scontext '=>'] tycls tyvar ['where' cdecls]
 classDeclParser :: TokParser Decl
-classDeclParser = withSpanAnn (DeclAnn . mkAnnotation) $ do
+classDeclParser = withSpanAnn (DeclAnn . mkSpanAnnotation) $ do
   expectedTok TkKeywordClass
   (context, classHead) <- declHeadWithOptionalContext classHeadParser
   classFundeps <- MP.option [] (MP.try classFundepsParser)
@@ -797,7 +797,7 @@
   determines <- MP.many lowerIdentifierParser
   pure $ \span' ->
     FunctionalDependency
-      { functionalDependencyAnns = [mkAnnotation span'],
+      { functionalDependencyAnns = [mkSpanAnnotation span'],
         functionalDependencyDeterminers = determinedBy,
         functionalDependencyDetermined = determines
       }
@@ -837,30 +837,30 @@
 
 classPragmaItemParser :: TokParser ClassDeclItem
 classPragmaItemParser =
-  withSpanAnn (ClassItemAnn . mkAnnotation) $
+  withSpanAnn (ClassItemAnn . mkSpanAnnotation) $
     ClassItemPragma <$> anyPragmaParser "pragma declaration"
 
 classTypeSigItemParser :: TokParser ClassDeclItem
-classTypeSigItemParser = typeSigItemParser (ClassItemAnn . mkAnnotation) ClassItemTypeSig
+classTypeSigItemParser = typeSigItemParser (ClassItemAnn . mkSpanAnnotation) ClassItemTypeSig
 
 classDefaultSigItemParser :: TokParser ClassDeclItem
-classDefaultSigItemParser = withSpanAnn (ClassItemAnn . mkAnnotation) $ do
+classDefaultSigItemParser = withSpanAnn (ClassItemAnn . mkSpanAnnotation) $ do
   expectedTok TkKeywordDefault
   name <- binderNameParser
   expectedTok TkReservedDoubleColon
   ClassItemDefaultSig name <$> typeSignatureParser
 
 classFixityItemParser :: TokParser ClassDeclItem
-classFixityItemParser = fixityItemParser (ClassItemAnn . mkAnnotation) ClassItemFixity
+classFixityItemParser = fixityItemParser (ClassItemAnn . mkSpanAnnotation) ClassItemFixity
 
 classDefaultItemParser :: TokParser ClassDeclItem
-classDefaultItemParser = valueItemParser (ClassItemAnn . mkAnnotation) ClassItemDefault
+classDefaultItemParser = valueItemParser (ClassItemAnn . mkSpanAnnotation) ClassItemDefault
 
 -- | Report core:
 --
 -- > topdecl -> 'instance' [scontext '=>'] qtycls inst ['where' idecls]
 instanceDeclParser :: TokParser Decl
-instanceDeclParser = withSpanAnn (DeclAnn . mkAnnotation) $ do
+instanceDeclParser = withSpanAnn (DeclAnn . mkSpanAnnotation) $ do
   expectedTok TkKeywordInstance
   overlapPragmas <- MP.option [] (fmap (: []) instanceOverlapPragmaParser)
   warningText <- MP.optional warningPragmaParser
@@ -879,7 +879,7 @@
         }
 
 standaloneDerivingDeclParser :: TokParser Decl
-standaloneDerivingDeclParser = withSpanAnn (DeclAnn . mkAnnotation) $ do
+standaloneDerivingDeclParser = withSpanAnn (DeclAnn . mkSpanAnnotation) $ do
   expectedTok TkKeywordDeriving
   strategy <- MP.optional derivingStrategyParser
   expectedTok TkKeywordInstance
@@ -925,18 +925,18 @@
         _ -> (if typeSigPrefix then instanceTypeSigItemParser else instanceValueItemParser)
 
 instancePragmaItemParser :: TokParser InstanceDeclItem
-instancePragmaItemParser = withSpanAnn (InstanceItemAnn . mkAnnotation) $ do
+instancePragmaItemParser = withSpanAnn (InstanceItemAnn . mkSpanAnnotation) $ do
   pragma <- anyPragmaParser "pragma declaration"
   pure (InstanceItemPragma pragma)
 
 instanceTypeSigItemParser :: TokParser InstanceDeclItem
-instanceTypeSigItemParser = typeSigItemParser (InstanceItemAnn . mkAnnotation) InstanceItemTypeSig
+instanceTypeSigItemParser = typeSigItemParser (InstanceItemAnn . mkSpanAnnotation) InstanceItemTypeSig
 
 instanceFixityItemParser :: TokParser InstanceDeclItem
-instanceFixityItemParser = fixityItemParser (InstanceItemAnn . mkAnnotation) InstanceItemFixity
+instanceFixityItemParser = fixityItemParser (InstanceItemAnn . mkSpanAnnotation) InstanceItemFixity
 
 instanceValueItemParser :: TokParser InstanceDeclItem
-instanceValueItemParser = valueItemParser (InstanceItemAnn . mkAnnotation) InstanceItemBind
+instanceValueItemParser = valueItemParser (InstanceItemAnn . mkSpanAnnotation) InstanceItemBind
 
 -- ---------------------------------------------------------------------------
 -- Shared class/instance item helpers
@@ -955,7 +955,7 @@
   ctor . functionBindValue headForm name pats <$> equationRhsParser
 
 foreignDeclParser :: TokParser Decl
-foreignDeclParser = withSpanAnn (DeclAnn . mkAnnotation) $ do
+foreignDeclParser = withSpanAnn (DeclAnn . mkSpanAnnotation) $ do
   expectedTok TkKeywordForeign
   direction <- foreignDirectionParser
   callConv <- callConvParser
@@ -1026,7 +1026,7 @@
 --
 -- > topdecl -> 'data' [context '=>'] simpletype ['=' constrs] [deriving]
 dataDeclParser :: TokParser Decl
-dataDeclParser = withSpanAnn (DeclAnn . mkAnnotation) $ do
+dataDeclParser = withSpanAnn (DeclAnn . mkSpanAnnotation) $ do
   expectedTok TkKeywordData
   ctypePragma <- optionalHiddenPragma Just
   (context, typeHead, inlineKind) <- declHeadPreferringInlineKind typeDeclHeadParser
@@ -1050,7 +1050,7 @@
 --   - No strictness annotations in constructors
 --   - No deriving clause
 typeDataDeclParser :: TokParser Decl
-typeDataDeclParser = withSpanAnn (DeclAnn . mkAnnotation) $ do
+typeDataDeclParser = withSpanAnn (DeclAnn . mkSpanAnnotation) $ do
   expectedTok TkKeywordType
   expectedTok TkKeywordData
   -- type data may not have a datatype context
@@ -1079,7 +1079,7 @@
 -- | Parse constructors for type data (traditional style, after `=`)
 -- No labelled fields, no strictness annotations
 typeDataConDeclParser :: TokParser DataConDecl
-typeDataConDeclParser = withSpanAnn (DataConAnn . mkAnnotation) $ do
+typeDataConDeclParser = withSpanAnn (DataConAnn . mkSpanAnnotation) $ do
   (_forallVars, context) <- dataConQualifiersParser
   MP.try (typeDataConPrefixParser context) <|> typeDataConInfixParser context
 
@@ -1117,7 +1117,7 @@
 -- | Parse a GADT constructor for type data
 -- Only equality constraints permitted, no strictness, no records
 gadtTypeDataConDeclParser :: TokParser DataConDecl
-gadtTypeDataConDeclParser = withSpanAnn (DataConAnn . mkAnnotation) $ do
+gadtTypeDataConDeclParser = withSpanAnn (DataConAnn . mkSpanAnnotation) $ do
   -- Parse constructor names (can be multiple separated by commas)
   names <- gadtConNameParser `MP.sepBy1` expectedTok TkSpecialComma
   expectedTok TkReservedDoubleColon
@@ -1147,7 +1147,7 @@
        in pure (GadtPrefixBody argsWithKinds resultTy)
 
 dataConDeclParser :: TokParser DataConDecl
-dataConDeclParser = withSpanAnn (DataConAnn . mkAnnotation) $ do
+dataConDeclParser = withSpanAnn (DataConAnn . mkSpanAnnotation) $ do
   (forallVars, context) <- dataConQualifiersParser
   tok <- peekToken
   case lexTokenKind tok of
@@ -1227,7 +1227,7 @@
 --
 -- > topdecl -> 'newtype' [context '=>'] simpletype '=' newconstr [deriving]
 newtypeDeclParser :: TokParser Decl
-newtypeDeclParser = withSpanAnn (DeclAnn . mkAnnotation) $ do
+newtypeDeclParser = withSpanAnn (DeclAnn . mkSpanAnnotation) $ do
   expectedTok TkKeywordNewtype
   ctypePragma <- optionalHiddenPragma Just
   (context, typeHead, inlineKind) <- declHeadPreferringInlineKind typeDeclHeadParser
@@ -1264,7 +1264,7 @@
 
 -- | Parse a GADT constructor declaration: @Con1, Con2 :: forall a. Ctx => Type@
 gadtConDeclParser :: TokParser DataConDecl
-gadtConDeclParser = withSpanAnn (DataConAnn . mkAnnotation) $ do
+gadtConDeclParser = withSpanAnn (DataConAnn . mkSpanAnnotation) $ do
   -- Parse constructor names (can be multiple separated by commas)
   names <- gadtConNameParser `MP.sepBy1` expectedTok TkSpecialComma
   expectedTok TkReservedDoubleColon
@@ -1458,7 +1458,7 @@
   withSpan $
     ( do
         ident <- typeParamBinderNameParser
-        pure (\span' -> TyVarBinder [mkAnnotation span'] ident Nothing TyVarBSpecified TyVarBVisible)
+        pure (\span' -> TyVarBinder [mkSpanAnnotation span'] ident Nothing TyVarBSpecified TyVarBVisible)
     )
       <|> ( do
               expectedTok TkSpecialLParen
@@ -1466,7 +1466,7 @@
               expectedTok TkReservedDoubleColon
               kind <- typeParser
               expectedTok TkSpecialRParen
-              pure (\span' -> TyVarBinder [mkAnnotation span'] ident (Just kind) TyVarBSpecified TyVarBVisible)
+              pure (\span' -> TyVarBinder [mkSpanAnnotation span'] ident (Just kind) TyVarBSpecified TyVarBVisible)
           )
 
 declTypeParamParser :: TokParser TyVarBinder
@@ -1477,7 +1477,7 @@
   expectedTok TkTypeApp
   ( do
       ident <- lowerIdentifierParser <|> (expectedTok TkKeywordUnderscore $> "_")
-      pure (\span' -> TyVarBinder [mkAnnotation span'] ident Nothing TyVarBSpecified TyVarBInvisible)
+      pure (\span' -> TyVarBinder [mkSpanAnnotation span'] ident Nothing TyVarBSpecified TyVarBInvisible)
     )
     <|> do
       expectedTok TkSpecialLParen
@@ -1485,7 +1485,7 @@
       expectedTok TkReservedDoubleColon
       kind <- typeParser
       expectedTok TkSpecialRParen
-      pure (\span' -> TyVarBinder [mkAnnotation span'] ident (Just kind) TyVarBSpecified TyVarBInvisible)
+      pure (\span' -> TyVarBinder [mkSpanAnnotation span'] ident (Just kind) TyVarBSpecified TyVarBInvisible)
 
 isTypeVarName :: Text -> Bool
 isTypeVarName name =
@@ -1614,7 +1614,7 @@
   fieldTy <- recordFieldBangTypeParser
   pure $ \span' ->
     FieldDecl
-      { fieldAnns = [mkAnnotation span'],
+      { fieldAnns = [mkSpanAnnotation span'],
         fieldNames = names,
         fieldMultiplicity = mMult,
         fieldType = fieldTy
@@ -1646,7 +1646,7 @@
   ty <- typeP
   pure $ \span' ->
     BangType
-      { bangAnns = [mkAnnotation span'],
+      { bangAnns = [mkSpanAnnotation span'],
         bangPragmas = pragmas,
         bangStrict = strict,
         bangLazy = isLazy,
@@ -1687,12 +1687,12 @@
 -- | Parse a pattern binding declaration like @(x, y) = (1, 2)@.
 -- This handles bindings where the LHS is a pattern rather than a function name.
 patternBindDeclParser :: TokParser Decl
-patternBindDeclParser = MP.try $ withSpanAnn (DeclAnn . mkAnnotation) $ do
+patternBindDeclParser = MP.try $ withSpanAnn (DeclAnn . mkSpanAnnotation) $ do
   pat <- region "while parsing pattern binding" patternParser
   DeclValue . PatternBind NoMultiplicityTag pat <$> equationRhsParser
 
 valueDeclParser :: TokParser Decl
-valueDeclParser = withSpanAnn (DeclAnn . mkAnnotation) $ do
+valueDeclParser = withSpanAnn (DeclAnn . mkSpanAnnotation) $ do
   (headForm, name, pats) <- functionHeadParserWith patParser apatParser
   functionBindDecl headForm name pats <$> equationRhsParser
 
@@ -1708,7 +1708,7 @@
 
 -- | Parse a pattern synonym type signature: @pattern Name1, Name2 :: Type@
 patternSynonymSigDeclParser :: TokParser Decl
-patternSynonymSigDeclParser = withSpanAnn (DeclAnn . mkAnnotation) $ do
+patternSynonymSigDeclParser = withSpanAnn (DeclAnn . mkSpanAnnotation) $ do
   expectedTok TkKeywordPattern
   names <- patSynNameParser `MP.sepBy1` expectedTok TkSpecialComma
   expectedTok TkReservedDoubleColon
@@ -1724,7 +1724,7 @@
 -- | Parse a pattern synonym declaration.
 -- Handles prefix, infix, and record forms with all three directionalities.
 patternSynonymDeclParser :: TokParser Decl
-patternSynonymDeclParser = withSpanAnn (DeclAnn . mkAnnotation) $ do
+patternSynonymDeclParser = withSpanAnn (DeclAnn . mkSpanAnnotation) $ do
   expectedTok TkKeywordPattern
   (name, args) <- patSynLhsParser
   (dir, pat) <- patSynDirAndPatParser name
@@ -1809,7 +1809,7 @@
   rhs <- equationRhsParser
   pure $ \span' ->
     Match
-      { matchAnns = [mkAnnotation span'],
+      { matchAnns = [mkSpanAnnotation span'],
         matchHeadForm = headForm,
         matchPats = pats,
         matchRhs = rhs
diff --git a/src/Aihc/Parser/Internal/Expr.hs b/src/Aihc/Parser/Internal/Expr.hs
--- a/src/Aihc/Parser/Internal/Expr.hs
+++ b/src/Aihc/Parser/Internal/Expr.hs
@@ -149,7 +149,7 @@
   multiWayIfExprParser <|> classicIfExprParser
 
 classicIfExprParser :: TokParser Expr
-classicIfExprParser = withSpanAnn (EAnn . mkAnnotation) $ do
+classicIfExprParser = withSpanAnn (EAnn . mkSpanAnnotation) $ do
   cond <- exprParser
   skipSemicolons
   expectedTok TkKeywordThen
@@ -159,7 +159,7 @@
   EIf cond yes <$> exprParser
 
 multiWayIfExprParser :: TokParser Expr
-multiWayIfExprParser = withSpanAnn (EAnn . mkAnnotation) $ do
+multiWayIfExprParser = withSpanAnn (EAnn . mkSpanAnnotation) $ do
   EMultiWayIf <$> braces (MP.some multiWayIfAlternative)
 
 multiWayIfAlternative :: TokParser (GuardedRhs Expr)
@@ -170,25 +170,25 @@
   body <- exprParser
   pure $ \span' ->
     GuardedRhs
-      { guardedRhsAnns = [mkAnnotation span'],
+      { guardedRhsAnns = [mkSpanAnnotation span'],
         guardedRhsGuards = guards,
         guardedRhsBody = body
       }
 
 doExprParser :: TokParser Expr
-doExprParser = withSpanAnn (EAnn . mkAnnotation) $ do
+doExprParser = withSpanAnn (EAnn . mkSpanAnnotation) $ do
   expectedTok TkKeywordDo
   stmts <- bracedSemiSep1 doStmtParser
   pure (EDo stmts DoPlain)
 
 mdoExprParser :: TokParser Expr
-mdoExprParser = withSpanAnn (EAnn . mkAnnotation) $ do
+mdoExprParser = withSpanAnn (EAnn . mkSpanAnnotation) $ do
   expectedTok TkKeywordMdo
   stmts <- bracedSemiSep1 doStmtParser
   pure (EDo stmts DoMdo)
 
 qualifiedDoExprParser :: TokParser Expr
-qualifiedDoExprParser = withSpanAnn (EAnn . mkAnnotation) $ do
+qualifiedDoExprParser = withSpanAnn (EAnn . mkSpanAnnotation) $ do
   modName <- tokenSatisfy "qualified do" $ \tok ->
     case lexTokenKind tok of
       TkQualifiedDo m -> Just m
@@ -197,7 +197,7 @@
   pure (EDo stmts (DoQualified modName))
 
 qualifiedMdoExprParser :: TokParser Expr
-qualifiedMdoExprParser = withSpanAnn (EAnn . mkAnnotation) $ do
+qualifiedMdoExprParser = withSpanAnn (EAnn . mkSpanAnnotation) $ do
   modName <- tokenSatisfy "qualified mdo" $ \tok ->
     case lexTokenKind tok of
       TkQualifiedMdo m -> Just m
@@ -207,7 +207,7 @@
 
 -- | Parse a proc expression: @proc apat -> cmd@
 procExprParser :: TokParser Expr
-procExprParser = withSpanAnn (EAnn . mkAnnotation) $ do
+procExprParser = withSpanAnn (EAnn . mkSpanAnnotation) $ do
   expectedTok TkKeywordProc
   pat <- region "while parsing proc pattern" apatParser
   expectedTok TkReservedRightArrow
@@ -224,7 +224,7 @@
 
 doBindOrExprStmtParser :: TokParser (DoStmt Expr)
 doBindOrExprStmtParser =
-  withSpanAnn (DoAnn . mkAnnotation) $
+  withSpanAnn (DoAnn . mkSpanAnnotation) $
     exprOrPatternBindParser exprParser (region "while parsing '<-' binding" exprParser) DoBind DoExpr
 
 parseLetDeclsParser :: TokParser [Decl]
@@ -241,12 +241,12 @@
 bracedDeclsMaybeEmpty = bracedSemiSep localDeclsParser
 
 doLetStmtParser :: TokParser (DoStmt Expr)
-doLetStmtParser = withSpanAnn (DoAnn . mkAnnotation) $ do
+doLetStmtParser = withSpanAnn (DoAnn . mkSpanAnnotation) $ do
   DoLetDecls <$> parseLetDeclsStmtParser
 
 -- | Parse a @rec@ statement inside a do-block.
 doRecStmtParser :: TokParser (DoStmt Expr)
-doRecStmtParser = withSpanAnn (DoAnn . mkAnnotation) $ do
+doRecStmtParser = withSpanAnn (DoAnn . mkSpanAnnotation) $ do
   expectedTok TkKeywordRec
   DoRecStmt <$> bracedSemiSep1 doStmtParser
 
@@ -379,7 +379,7 @@
 tokenExprParser :: String -> (LexToken -> Maybe Expr) -> TokParser Expr
 tokenExprParser expected matchToken =
   tokenSatisfy expected $ \tok ->
-    EAnn (mkAnnotation (lexTokenSpan tok)) <$> matchToken tok
+    EAnn (mkSpanAnnotation (lexTokenSpan tok)) <$> matchToken tok
 
 charExprParser :: TokParser Expr
 charExprParser =
@@ -427,7 +427,7 @@
       let !endInput = MP.stateInput endState
           !endOffset = MP.stateOffset endState
           appSpan = consumedSpan inputStart startInput startOffset endInput endOffset
-      pure (EAnn (mkAnnotation appSpan) (foldl applyArg first rest))
+      pure (EAnn (mkSpanAnnotation appSpan) (foldl applyArg first rest))
   where
     appArg :: TokParser (Either Type Expr)
     appArg = (Left <$> typeAppArg) <|> (Right <$> appExprArgParser)
@@ -522,7 +522,7 @@
     normalizeField (fieldName, mExpr, sp) =
       case mExpr of
         Just expr' -> RecordField fieldName expr' False
-        Nothing -> RecordField fieldName (EAnn (mkAnnotation sp) (EVar fieldName)) True
+        Nothing -> RecordField fieldName (EAnn (mkSpanAnnotation sp) (EVar fieldName)) True
 
     recordDotMayFollow :: Expr -> Bool
     recordDotMayFollow expr =
@@ -668,17 +668,17 @@
       if thAny then parser else varExprParser
 
 explicitTypeExprParser :: TokParser Expr
-explicitTypeExprParser = withSpanAnn (EAnn . mkAnnotation) $ do
+explicitTypeExprParser = withSpanAnn (EAnn . mkSpanAnnotation) $ do
   expectedTok TkKeywordType
   ETypeSyntax TypeSyntaxExplicitNamespace <$> typeParser
 
 prefixNegateAtomExprParserWith :: AtomContext -> TokParser Expr
-prefixNegateAtomExprParserWith atomContext = withSpanAnn (EAnn . mkAnnotation) $ do
+prefixNegateAtomExprParserWith atomContext = withSpanAnn (EAnn . mkSpanAnnotation) $ do
   prefixMinusTokenParser
   ENegate <$> atomExprParserWith atomContext
 
 negateExprParser :: TokParser Expr
-negateExprParser = withSpanAnn (EAnn . mkAnnotation) $ do
+negateExprParser = withSpanAnn (EAnn . mkSpanAnnotation) $ do
   _ <- minusTokenValueParser
   ENegate <$> negateOperandParser
 
@@ -722,7 +722,7 @@
 
 parenOperatorExprParser :: TokParser Expr
 parenOperatorExprParser =
-  withSpanAnn (EAnn . mkAnnotation) $
+  withSpanAnn (EAnn . mkSpanAnnotation) $
     EVar <$> parens operatorExprNameParser
 
 -- | Parse the operator inside a parenthesized operator expression such as
@@ -794,7 +794,7 @@
   rhsArrowTok arrowKind
   body <- region (rhsContextText arrowKind) bodyParser
   whereDecls <- MP.optional whereClauseParser
-  pure (\span' -> UnguardedRhs [mkAnnotation span'] body whereDecls)
+  pure (\span' -> UnguardedRhs [mkSpanAnnotation span'] body whereDecls)
 
 rhsContextText :: RhsArrowKind -> Text
 rhsContextText RhsArrowCase = "while parsing case alternative right-hand side"
@@ -804,7 +804,7 @@
 guardedRhssParserWithBodyParser arrowKind bodyParser = withSpan $ do
   grhss <- MP.some (guardedRhsParserWithBodyParser arrowKind bodyParser)
   whereDecls <- MP.optional whereClauseParser
-  pure (\span' -> GuardedRhss [mkAnnotation span'] grhss whereDecls)
+  pure (\span' -> GuardedRhss [mkSpanAnnotation span'] grhss whereDecls)
 
 guardedRhsParserWithBodyParser :: RhsArrowKind -> TokParser body -> TokParser (GuardedRhs body)
 guardedRhsParserWithBodyParser arrowKind bodyParser = withSpan $ do
@@ -814,7 +814,7 @@
   body <- bodyParser
   pure $ \span' ->
     GuardedRhs
-      { guardedRhsAnns = [mkAnnotation span'],
+      { guardedRhsAnns = [mkSpanAnnotation span'],
         guardedRhsGuards = guards,
         guardedRhsBody = body
       }
@@ -835,7 +835,7 @@
 -- | Parse a guard expression or pattern bind.
 guardBindOrExprParser :: RhsArrowKind -> TokParser GuardQualifier
 guardBindOrExprParser arrowKind =
-  withSpanAnn (GuardAnn . mkAnnotation) $
+  withSpanAnn (GuardAnn . mkSpanAnnotation) $
     exprOrPatternBindParser
       (guardExprParser arrowKind)
       exprParser
@@ -843,7 +843,7 @@
       GuardExpr
 
 guardLetParser :: TokParser GuardQualifier
-guardLetParser = withSpanAnn (GuardAnn . mkAnnotation) $ do
+guardLetParser = withSpanAnn (GuardAnn . mkSpanAnnotation) $ do
   GuardLet <$> parseLetDeclsStmtParser
 
 -- | Parse a guard expression.  Equation guards allow a bare top-level type
@@ -860,7 +860,7 @@
   rhs <- region "while parsing case alternative" rhsParser
   pure $ \span' ->
     CaseAlt
-      { caseAltAnns = [mkAnnotation span'],
+      { caseAltAnns = [mkSpanAnnotation span'],
         caseAltPattern = pat,
         caseAltRhs = rhs
       }
@@ -871,13 +871,13 @@
   rhs <- region "while parsing lambda-cases alternative" rhsParser
   pure $ \span' ->
     LambdaCaseAlt
-      { lambdaCaseAltAnns = [mkAnnotation span'],
+      { lambdaCaseAltAnns = [mkSpanAnnotation span'],
         lambdaCaseAltPats = pats,
         lambdaCaseAltRhs = rhs
       }
 
 caseExprParser :: TokParser Expr
-caseExprParser = withSpanAnn (EAnn . mkAnnotation) $ do
+caseExprParser = withSpanAnn (EAnn . mkSpanAnnotation) $ do
   expectedTok TkKeywordCase
   scrutinee <- region "while parsing case expression" exprParser
   expectedTok TkKeywordOf
@@ -886,7 +886,7 @@
     bracedAlts = bracedSemiSep caseAltParser
 
 parenExprParser :: TokParser Expr
-parenExprParser = withSpanAnn (EAnn . mkAnnotation) $ do
+parenExprParser = withSpanAnn (EAnn . mkSpanAnnotation) $ do
   (tupleFlavor, closeTok) <- tupleDelimsParser
   closedImmediately <- optionalTok closeTok
   if closedImmediately
@@ -1073,7 +1073,7 @@
       pure (EUnboxedSum altIdx arity inner)
 
 listExprParser :: TokParser Expr
-listExprParser = withSpanAnn (EAnn . mkAnnotation) $ do
+listExprParser = withSpanAnn (EAnn . mkSpanAnnotation) $ do
   expectedTok TkSpecialLBracket
   closedImmediately <- optionalTok TkSpecialRBracket
   if closedImmediately
@@ -1141,7 +1141,7 @@
 -- @then group by e using f@, or @then group using f@.
 -- Only attempted when the 'TransformListComp' extension is enabled.
 compTransformStmtParser :: TokParser CompStmt
-compTransformStmtParser = MP.try $ withSpanAnn (CompAnn . mkAnnotation) $ do
+compTransformStmtParser = MP.try $ withSpanAnn (CompAnn . mkSpanAnnotation) $ do
   enabled <- isExtensionEnabled TransformListComp
   guard enabled
   expectedTok TkKeywordThen
@@ -1206,7 +1206,7 @@
     <|> compTransformAppExprParser
 
 compTransformLambdaExprParser :: TokParser Expr
-compTransformLambdaExprParser = withSpanAnn (EAnn . mkAnnotation) $ do
+compTransformLambdaExprParser = withSpanAnn (EAnn . mkSpanAnnotation) $ do
   expectedTok TkReservedBackslash
   lambdaCaseParser <|> lambdaCasesParser <|> lambdaPatsParser
   where
@@ -1232,7 +1232,7 @@
   rhs <- region "while parsing case alternative" (caseRhsParserWithBodyParser compTransformExprParser)
   pure $ \span' ->
     CaseAlt
-      { caseAltAnns = [mkAnnotation span'],
+      { caseAltAnns = [mkSpanAnnotation span'],
         caseAltPattern = pat,
         caseAltRhs = rhs
       }
@@ -1243,7 +1243,7 @@
   rhs <- region "while parsing lambda-cases alternative" (caseRhsParserWithBodyParser compTransformExprParser)
   pure $ \span' ->
     LambdaCaseAlt
-      { lambdaCaseAltAnns = [mkAnnotation span'],
+      { lambdaCaseAltAnns = [mkSpanAnnotation span'],
         lambdaCaseAltPats = pats,
         lambdaCaseAltRhs = rhs
       }
@@ -1252,7 +1252,7 @@
 compTransformAppExprParser = appExprParserWith compTransformAtomOrRecordExprParser
 
 compTransformNegateExprParser :: TokParser Expr
-compTransformNegateExprParser = withSpanAnn (EAnn . mkAnnotation) $ do
+compTransformNegateExprParser = withSpanAnn (EAnn . mkSpanAnnotation) $ do
   _ <- minusTokenValueParser
   ENegate <$> compTransformNegateOperandParser
 
@@ -1271,18 +1271,18 @@
 
 compGenOrGuardParser :: TokParser CompStmt
 compGenOrGuardParser =
-  withSpanAnn (CompAnn . mkAnnotation) $
+  withSpanAnn (CompAnn . mkSpanAnnotation) $
     exprOrPatternBindParser exprParser (region "while parsing '<-' generator" exprParser) CompGen CompGuard
 
 compLetStmtParser :: TokParser CompStmt
-compLetStmtParser = withSpanAnn (CompAnn . mkAnnotation) $ do
+compLetStmtParser = withSpanAnn (CompAnn . mkSpanAnnotation) $ do
   CompLetDecls <$> parseLetDeclsStmtParser
 
 -- | Report core:
 --
 -- > lexp -> '\\' apat_1 ... apat_n '->' exp
 lambdaExprParser :: TokParser Expr
-lambdaExprParser = withSpanAnn (EAnn . mkAnnotation) $ do
+lambdaExprParser = withSpanAnn (EAnn . mkSpanAnnotation) $ do
   expectedTok TkReservedBackslash
   lambdaCaseParser <|> lambdaCasesParser <|> lambdaPatsParser
   where
@@ -1309,7 +1309,7 @@
 --
 -- > lexp -> 'let' decls 'in' exp
 letExprParser :: TokParser Expr
-letExprParser = withSpanAnn (EAnn . mkAnnotation) $ do
+letExprParser = withSpanAnn (EAnn . mkSpanAnnotation) $ do
   decls <- parseLetDeclsParser
   expectedTok TkKeywordIn
   ELetDecls decls <$> exprParser
@@ -1335,7 +1335,7 @@
 
 localTypeSigDeclsParser :: TokParser Decl
 localTypeSigDeclsParser =
-  withSpanAnn (DeclAnn . mkAnnotation) $
+  withSpanAnn (DeclAnn . mkSpanAnnotation) $
     typedBindingOrSignatureParser
       typeParser
       DeclTypeSig
@@ -1347,7 +1347,7 @@
       "local typed bindings with '=' or guards require exactly one binder"
 
 localFunctionDeclParser :: TokParser Decl
-localFunctionDeclParser = withSpanAnn (DeclAnn . mkAnnotation) $ do
+localFunctionDeclParser = withSpanAnn (DeclAnn . mkSpanAnnotation) $ do
   -- Only the head can require a retry as a pattern binding. A failed body
   -- must not cause the same nested declarations to be parsed again.
   (headForm, name, pats) <- MP.try $ do
@@ -1357,7 +1357,7 @@
   functionBindDecl headForm name pats <$> equationRhsParser
 
 localPatternDeclParser :: TokParser Decl
-localPatternDeclParser = withSpanAnn (DeclAnn . mkAnnotation) $ do
+localPatternDeclParser = withSpanAnn (DeclAnn . mkSpanAnnotation) $ do
   linearEnabled <- isExtensionEnabled LinearTypes
   multTag <-
     if linearEnabled
@@ -1375,7 +1375,7 @@
     _ -> ExplicitMultiplicityTag <$> typeAtomParser
 
 implicitParamDeclParser :: TokParser Decl
-implicitParamDeclParser = withSpanAnn (DeclAnn . mkAnnotation) $ do
+implicitParamDeclParser = withSpanAnn (DeclAnn . mkSpanAnnotation) $ do
   name <- implicitParamNameParser
   expectedTok TkReservedEquals
   rhsExpr <- exprParser
@@ -1386,7 +1386,7 @@
 varExprParser =
   tokenSatisfy "identifier" $ \tok ->
     case identifierName tok of
-      Just name -> Just (EAnn (mkAnnotation (lexTokenSpan tok)) (EVar name))
+      Just name -> Just (EAnn (mkSpanAnnotation (lexTokenSpan tok)) (EVar name))
       Nothing -> Nothing
 
 implicitParamExprParser :: TokParser Expr
@@ -1396,7 +1396,7 @@
       TkImplicitParam name ->
         Just $
           EAnn
-            (mkAnnotation (lexTokenSpan tok))
+            (mkSpanAnnotation (lexTokenSpan tok))
             (EImplicitParam name)
       _ -> Nothing
 
@@ -1407,7 +1407,7 @@
       TkKeywordUnderscore ->
         Just $
           EAnn
-            (mkAnnotation (lexTokenSpan tok))
+            (mkSpanAnnotation (lexTokenSpan tok))
             (EVar (mkNameAt tok Nothing NameVarId "_"))
       _ -> Nothing
 
@@ -1421,27 +1421,27 @@
     <|> thPatQuoteParser
 
 thExpQuoteParser :: TokParser Expr
-thExpQuoteParser = thQuoteParser (EAnn . mkAnnotation) TkTHExpQuoteOpen TkTHExpQuoteClose exprParser ETHExpQuote
+thExpQuoteParser = thQuoteParser (EAnn . mkSpanAnnotation) TkTHExpQuoteOpen TkTHExpQuoteClose exprParser ETHExpQuote
 
 thTypedQuoteParser :: TokParser Expr
-thTypedQuoteParser = thQuoteParser (EAnn . mkAnnotation) TkTHTypedQuoteOpen TkTHTypedQuoteClose exprParser ETHTypedQuote
+thTypedQuoteParser = thQuoteParser (EAnn . mkSpanAnnotation) TkTHTypedQuoteOpen TkTHTypedQuoteClose exprParser ETHTypedQuote
 
 thDeclQuoteParser :: TokParser Expr
-thDeclQuoteParser = thQuoteParser (EAnn . mkAnnotation) TkTHDeclQuoteOpen TkTHExpQuoteClose (bracedSemiSep declParser <|> plainSemiSep declParser) ETHDeclQuote
+thDeclQuoteParser = thQuoteParser (EAnn . mkSpanAnnotation) TkTHDeclQuoteOpen TkTHExpQuoteClose (bracedSemiSep declParser <|> plainSemiSep declParser) ETHDeclQuote
 
 thTypeQuoteParser :: TokParser Expr
-thTypeQuoteParser = thQuoteParser (EAnn . mkAnnotation) TkTHTypeQuoteOpen TkTHExpQuoteClose typeParser ETHTypeQuote
+thTypeQuoteParser = thQuoteParser (EAnn . mkSpanAnnotation) TkTHTypeQuoteOpen TkTHExpQuoteClose typeParser ETHTypeQuote
 
 thPatQuoteParser :: TokParser Expr
-thPatQuoteParser = thQuoteParser (EAnn . mkAnnotation) TkTHPatQuoteOpen TkTHExpQuoteClose patParser ETHPatQuote
+thPatQuoteParser = thQuoteParser (EAnn . mkSpanAnnotation) TkTHPatQuoteOpen TkTHExpQuoteClose patParser ETHPatQuote
 
 thUntypedSpliceParser :: TokParser Expr
-thUntypedSpliceParser = withSpanAnn (EAnn . mkAnnotation) $ do
+thUntypedSpliceParser = withSpanAnn (EAnn . mkSpanAnnotation) $ do
   expectedTok TkTHSplice
   ETHSplice <$> compactSpliceBodyParser
 
 thTypedSpliceParser :: TokParser Expr
-thTypedSpliceParser = withSpanAnn (EAnn . mkAnnotation) $ do
+thTypedSpliceParser = withSpanAnn (EAnn . mkSpanAnnotation) $ do
   expectedTok TkTHTypedSplice
   ETHTypedSplice <$> compactSpliceBodyParser
 
@@ -1465,7 +1465,7 @@
 thNameQuoteExprParser = thValueNameQuoteParser <|> thTypeNameQuoteParser
 
 thValueNameQuoteParser :: TokParser Expr
-thValueNameQuoteParser = withSpanAnn (EAnn . mkAnnotation) $ do
+thValueNameQuoteParser = withSpanAnn (EAnn . mkSpanAnnotation) $ do
   expectedTok TkTHQuoteTick
   body <- atomExprParser
   guard (isTHValueNameQuoteBody body)
@@ -1483,7 +1483,7 @@
     isTupleConstructorSlot Just {} = False
 
 thTypeNameQuoteParser :: TokParser Expr
-thTypeNameQuoteParser = withSpanAnn (EAnn . mkAnnotation) $ do
+thTypeNameQuoteParser = withSpanAnn (EAnn . mkSpanAnnotation) $ do
   expectedTok TkTHTypeQuoteTick
   body <- typeAtomParser
   guard (isTHTypeNameQuoteBody body)
@@ -1502,5 +1502,5 @@
 quasiQuoteExprParser =
   tokenSatisfy "quasi quote" $ \tok ->
     case lexTokenKind tok of
-      TkQuasiQuote quoter body -> Just (EAnn (mkAnnotation (lexTokenSpan tok)) (EQuasiQuote quoter body))
+      TkQuasiQuote quoter body -> Just (EAnn (mkSpanAnnotation (lexTokenSpan tok)) (EQuasiQuote quoter body))
       _ -> Nothing
diff --git a/src/Aihc/Parser/Internal/Import.hs b/src/Aihc/Parser/Internal/Import.hs
--- a/src/Aihc/Parser/Internal/Import.hs
+++ b/src/Aihc/Parser/Internal/Import.hs
@@ -37,7 +37,7 @@
   expectedTok TkKeywordWhere
   pure $ \span' ->
     ModuleHead
-      { moduleHeadAnns = [mkAnnotation span'],
+      { moduleHeadAnns = [mkSpanAnnotation span'],
         moduleHeadName = name,
         moduleHeadWarningPragma = mWarning,
         moduleHeadExports = exports
@@ -54,7 +54,7 @@
 exportSpecListParser = parens $ exportSpecParser `MP.sepEndBy` expectedTok TkSpecialComma
 
 exportSpecParser :: TokParser ExportSpec
-exportSpecParser = withSpanAnn (ExportAnn . mkAnnotation) $ do
+exportSpecParser = withSpanAnn (ExportAnn . mkSpanAnnotation) $ do
   mWarning <- MP.optional warningPragmaParser
   exportModuleParser mWarning <|> exportNameParser mWarning
 
@@ -168,7 +168,7 @@
   let isQualified = preQualified || isJust postQualified
   pure $ \span' ->
     ImportDecl
-      { importDeclAnns = [mkAnnotation span'],
+      { importDeclAnns = [mkSpanAnnotation span'],
         importDeclLevel = importedLevel,
         importDeclPackage = importedPackage,
         importDeclSourcePragma = importedSource,
@@ -195,13 +195,13 @@
   items <- parens $ importItemParser `MP.sepEndBy` expectedTok TkSpecialComma
   pure $ \span' ->
     ImportSpec
-      { importSpecAnns = [mkAnnotation span'],
+      { importSpecAnns = [mkSpanAnnotation span'],
         importSpecHiding = isHiding,
         importSpecItems = items
       }
 
 importItemParser :: TokParser ImportItem
-importItemParser = withSpanAnn (ImportAnn . mkAnnotation) $ do
+importItemParser = withSpanAnn (ImportAnn . mkSpanAnnotation) $ do
   namespace <- MP.optional exportImportNamespaceParser
   itemName <- identifierUnqualifiedNameParser <|> parens importOperatorParser
   -- When there's no explicit namespace, we still need to try parsing members
diff --git a/src/Aihc/Parser/Internal/Module.hs b/src/Aihc/Parser/Internal/Module.hs
--- a/src/Aihc/Parser/Internal/Module.hs
+++ b/src/Aihc/Parser/Internal/Module.hs
@@ -23,7 +23,7 @@
 import Aihc.Parser.Internal.Decl (declParser)
 import Aihc.Parser.Internal.Import (importDeclParser, languagePragmaParser, moduleHeaderParser)
 import Aihc.Parser.Lex (LexTokenKind (..), lexTokenKind)
-import Aihc.Parser.Syntax (Decl, ImportDecl, Module (..), mkAnnotation)
+import Aihc.Parser.Syntax (Decl, ImportDecl, Module (..), mkSpanAnnotation)
 import Control.Monad (void)
 import Text.Megaparsec qualified as MP
 
@@ -55,7 +55,7 @@
   let moduleSpan = consumedSpan inputStart startInput startOffset (MP.stateInput finalState) (MP.stateOffset finalState)
   pure
     Module
-      { moduleAnns = [mkAnnotation moduleSpan],
+      { moduleAnns = [mkSpanAnnotation moduleSpan],
         moduleHead = mHeader,
         moduleLanguagePragmas = concat languagePragmas,
         moduleImports = imports,
diff --git a/src/Aihc/Parser/Internal/Pattern.hs b/src/Aihc/Parser/Internal/Pattern.hs
--- a/src/Aihc/Parser/Internal/Pattern.hs
+++ b/src/Aihc/Parser/Internal/Pattern.hs
@@ -156,27 +156,27 @@
     _ -> varOrConPatternParser
 
 typeBinderPatternParser :: TokParser Pattern
-typeBinderPatternParser = withSpanAnn (PAnn . mkAnnotation) $ do
+typeBinderPatternParser = withSpanAnn (PAnn . mkSpanAnnotation) $ do
   expectedTok TkTypeApp
   PTypeBinder <$> visibleTypeBinderCoreParser
 
 explicitTypePatternParser :: TokParser Pattern
-explicitTypePatternParser = withSpanAnn (PAnn . mkAnnotation) $ do
+explicitTypePatternParser = withSpanAnn (PAnn . mkSpanAnnotation) $ do
   expectedTok TkKeywordType
   PTypeSyntax TypeSyntaxExplicitNamespace <$> typeParser
 
 strictPatternParser :: TokParser Pattern
-strictPatternParser = withSpanAnn (PAnn . mkAnnotation) $ do
+strictPatternParser = withSpanAnn (PAnn . mkSpanAnnotation) $ do
   expectedTok TkPrefixBang
   PStrict <$> apatParser
 
 irrefutablePatternParser :: TokParser Pattern
-irrefutablePatternParser = withSpanAnn (PAnn . mkAnnotation) $ do
+irrefutablePatternParser = withSpanAnn (PAnn . mkSpanAnnotation) $ do
   expectedTok TkPrefixTilde
   PIrrefutable <$> apatParser
 
 negativeLiteralPatternParser :: TokParser Pattern
-negativeLiteralPatternParser = MP.try $ withSpanAnn (PAnn . mkAnnotation) $ do
+negativeLiteralPatternParser = MP.try $ withSpanAnn (PAnn . mkSpanAnnotation) $ do
   expectedTok (TkVarSym "-")
   PNegLit <$> numericLiteralParser
 
@@ -189,21 +189,21 @@
 wildcardPatternParser =
   tokenSatisfy "wildcard" $ \tok ->
     case lexTokenKind tok of
-      TkKeywordUnderscore -> Just (PAnn (mkAnnotation (lexTokenSpan tok)) PWildcard)
+      TkKeywordUnderscore -> Just (PAnn (mkSpanAnnotation (lexTokenSpan tok)) PWildcard)
       _ -> Nothing
 
 literalPatternParser :: TokParser Pattern
 literalPatternParser =
   tokenSatisfy "literal" $ \tok -> do
     lit <- literalFromToken tok
-    let ann = mkAnnotation (lexTokenSpan tok)
+    let ann = mkSpanAnnotation (lexTokenSpan tok)
     pure (PAnn ann (PLit (LitAnn ann lit)))
 
 quasiQuotePatternParser :: TokParser Pattern
 quasiQuotePatternParser =
   tokenSatisfy "quasi quote" $ \tok ->
     case lexTokenKind tok of
-      TkQuasiQuote quoter body -> Just (PAnn (mkAnnotation (lexTokenSpan tok)) (PQuasiQuote quoter body))
+      TkQuasiQuote quoter body -> Just (PAnn (mkSpanAnnotation (lexTokenSpan tok)) (PQuasiQuote quoter body))
       _ -> Nothing
 
 literalParser :: TokParser Literal
@@ -212,7 +212,7 @@
 tokenLiteralParser :: String -> (LexToken -> Maybe Literal) -> TokParser Literal
 tokenLiteralParser expected matchToken =
   tokenSatisfy expected $ \tok ->
-    LitAnn (mkAnnotation (lexTokenSpan tok)) <$> matchToken tok
+    LitAnn (mkSpanAnnotation (lexTokenSpan tok)) <$> matchToken tok
 
 literalFromToken :: LexToken -> Maybe Literal
 literalFromToken tok =
@@ -265,7 +265,7 @@
 
 -- | Parse Template Haskell pattern splice: $pat or $(pat)
 thSplicePatternParser :: TokParser Pattern
-thSplicePatternParser = withSpanAnn (PAnn . mkAnnotation) $ do
+thSplicePatternParser = withSpanAnn (PAnn . mkSpanAnnotation) $ do
   expectedTok TkTHSplice
   tok <- peekToken
   case lexTokenKind tok of
@@ -277,7 +277,7 @@
   withSpan $
     ( do
         ident <- tyVarNameParser
-        pure (\span' -> TyVarBinder [mkAnnotation span'] ident Nothing TyVarBSpecified TyVarBInvisible)
+        pure (\span' -> TyVarBinder [mkSpanAnnotation span'] ident Nothing TyVarBSpecified TyVarBInvisible)
     )
       <|> ( do
               expectedTok TkSpecialLParen
@@ -285,14 +285,14 @@
               expectedTok TkReservedDoubleColon
               kind <- typeParser
               expectedTok TkSpecialRParen
-              pure (\span' -> TyVarBinder [mkAnnotation span'] ident (Just kind) TyVarBSpecified TyVarBInvisible)
+              pure (\span' -> TyVarBinder [mkSpanAnnotation span'] ident (Just kind) TyVarBSpecified TyVarBInvisible)
           )
 
 varOrConPatternParser :: TokParser Pattern
 varOrConPatternParser = do
   (tok, name) <- identifierNameWithTokenParser
   mNextTok <- peekTokenMaybe
-  let ann = mkAnnotation (lexTokenSpan tok)
+  let ann = mkSpanAnnotation (lexTokenSpan tok)
   case mNextTok of
     Just nextTok
       | isConLikeName name && lexTokenKind nextTok == TkSpecialLBrace -> do
@@ -300,7 +300,7 @@
           endInput <- MP.getInput
           let startSpan = lexTokenSpan tok
               recordSpan = maybe startSpan (mergeSourceSpans startSpan . lexTokenSpan) (tokStreamPrevToken endInput)
-          pure (PAnn (mkAnnotation recordSpan) (PRecord name fields hasWildcard))
+          pure (PAnn (mkSpanAnnotation recordSpan) (PRecord name fields hasWildcard))
     _ ->
       pure $
         PAnn ann $
@@ -326,7 +326,7 @@
   recordFieldsWithWildcardsParser (recordFieldPatternParser `MP.sepEndBy` expectedTok TkSpecialComma)
 
 listPatternParser :: TokParser Pattern
-listPatternParser = withSpanAnn (PAnn . mkAnnotation) $ do
+listPatternParser = withSpanAnn (PAnn . mkSpanAnnotation) $ do
   expectedTok TkSpecialLBracket
   elems <- listPatternElementParser `MP.sepBy` expectedTok TkSpecialComma
   expectedTok TkSpecialRBracket
@@ -370,7 +370,7 @@
     Nothing -> patternParser
 
 parenOrTuplePatternParser :: TokParser Pattern
-parenOrTuplePatternParser = withSpanAnn (PAnn . mkAnnotation) $ do
+parenOrTuplePatternParser = withSpanAnn (PAnn . mkSpanAnnotation) $ do
   (tupleFlavor, closeTok) <- tupleDelimsParser
   mNextTok <- peekTokenMaybe
   case fmap lexTokenKind mNextTok of
@@ -498,7 +498,7 @@
         operatorPatternParser :: TokParser Pattern
         operatorPatternParser = do
           tok' <- anySingle
-          let ann = mkAnnotation (lexTokenSpan tok')
+          let ann = mkSpanAnnotation (lexTokenSpan tok')
           case lexTokenKind tok' of
             TkVarSym op -> pure (PAnn ann (PVar (mkUnqualifiedNameAt tok' NameVarSym op)))
             TkConSym op -> pure (PAnn ann (PCon (mkNameAt tok' Nothing NameConSym op) [] []))
diff --git a/src/Aihc/Parser/Internal/Type.hs b/src/Aihc/Parser/Internal/Type.hs
--- a/src/Aihc/Parser/Internal/Type.hs
+++ b/src/Aihc/Parser/Internal/Type.hs
@@ -29,15 +29,15 @@
 
 -- | Parse a Template Haskell type splice: $typ or $(typ)
 thSpliceTypeParser :: TokParser Type
-thSpliceTypeParser = withSpanAnn (TAnn . mkAnnotation) $ do
+thSpliceTypeParser = withSpanAnn (TAnn . mkSpanAnnotation) $ do
   expectedTok TkTHSplice
   body <- parenSpliceBody <|> bareSpliceBody
   pure (TSplice body)
   where
-    parenSpliceBody = withSpanAnn (EAnn . mkAnnotation) $ do
+    parenSpliceBody = withSpanAnn (EAnn . mkSpanAnnotation) $ do
       body <- parens exprParser
       pure (EParen body)
-    bareSpliceBody = withSpanAnn (EAnn . mkAnnotation) $ do
+    bareSpliceBody = withSpanAnn (EAnn . mkSpanAnnotation) $ do
       EVar <$> identifierNameParser
 
 -- | Report core plus outer extension forms:
@@ -63,7 +63,7 @@
 --
 -- > forall a_1 ... a_n . type
 forallSignatureTypeParser :: TokParser Type
-forallSignatureTypeParser = withSpanAnn (TAnn . mkAnnotation) $ do
+forallSignatureTypeParser = withSpanAnn (TAnn . mkSpanAnnotation) $ do
   telescope <- forallTelescopeParser
   attachForallBody telescope <$> typeSignatureParser
 
@@ -98,7 +98,7 @@
 --
 -- > forall a_1 ... a_n . type
 forallTypeParser :: TokParser Type
-forallTypeParser = withSpanAnn (TAnn . mkAnnotation) $ do
+forallTypeParser = withSpanAnn (TAnn . mkSpanAnnotation) $ do
   telescope <- forallTelescopeParser
   attachForallBody telescope <$> typeParser
 
@@ -131,7 +131,7 @@
         ident <- tyVarNameParser
         mKind <- optionalTokThen TkReservedDoubleColon typeParser
         expectedTok TkSpecialRBrace
-        pure (\span' -> TyVarBinder [mkAnnotation span'] ident mKind TyVarBInferred TyVarBVisible)
+        pure (\span' -> TyVarBinder [mkSpanAnnotation span'] ident mKind TyVarBInferred TyVarBVisible)
     )
       <|> ( do
               expectedTok TkSpecialLParen
@@ -139,11 +139,11 @@
               expectedTok TkReservedDoubleColon
               kind <- typeParser
               expectedTok TkSpecialRParen
-              pure (\span' -> TyVarBinder [mkAnnotation span'] ident (Just kind) TyVarBSpecified TyVarBVisible)
+              pure (\span' -> TyVarBinder [mkSpanAnnotation span'] ident (Just kind) TyVarBSpecified TyVarBVisible)
           )
       <|> ( do
               ident <- tyVarNameParser
-              pure (\span' -> TyVarBinder [mkAnnotation span'] ident Nothing TyVarBSpecified TyVarBVisible)
+              pure (\span' -> TyVarBinder [mkSpanAnnotation span'] ident Nothing TyVarBSpecified TyVarBVisible)
           )
 
 contextTypeParser :: TokParser Type
@@ -420,7 +420,7 @@
 -- outer kind-signature parser in a plain type context and rejected in a
 -- signature context.
 typeImplicitParamParser :: TokParser Type
-typeImplicitParamParser = withSpanAnn (TAnn . mkAnnotation) $ do
+typeImplicitParamParser = withSpanAnn (TAnn . mkSpanAnnotation) $ do
   name <- implicitParamNameParser
   expectedTok TkReservedDoubleColon
   TImplicitParam name <$> typeSignatureParser
@@ -429,20 +429,20 @@
 typeWildcardParser =
   tokenSatisfy "wildcard" $ \tok ->
     case lexTokenKind tok of
-      TkKeywordUnderscore -> Just (TAnn (mkAnnotation (lexTokenSpan tok)) TWildcard)
+      TkKeywordUnderscore -> Just (TAnn (mkSpanAnnotation (lexTokenSpan tok)) TWildcard)
       _ -> Nothing
 
 typeLiteralTypeParser :: TokParser Type
 typeLiteralTypeParser =
   tokenSatisfy "type literal" $ \tok ->
     case lexTokenKind tok of
-      TkInteger n _ -> Just (TAnn (mkAnnotation (lexTokenSpan tok)) (TTypeLit (TypeLitInteger n (lexTokenText tok))))
-      TkString s -> Just (TAnn (mkAnnotation (lexTokenSpan tok)) (TTypeLit (TypeLitSymbol s (lexTokenText tok))))
-      TkChar c -> Just (TAnn (mkAnnotation (lexTokenSpan tok)) (TTypeLit (TypeLitChar c (lexTokenText tok))))
+      TkInteger n _ -> Just (TAnn (mkSpanAnnotation (lexTokenSpan tok)) (TTypeLit (TypeLitInteger n (lexTokenText tok))))
+      TkString s -> Just (TAnn (mkSpanAnnotation (lexTokenSpan tok)) (TTypeLit (TypeLitSymbol s (lexTokenText tok))))
+      TkChar c -> Just (TAnn (mkSpanAnnotation (lexTokenSpan tok)) (TTypeLit (TypeLitChar c (lexTokenText tok))))
       _ -> Nothing
 
 promotedTypeParser :: TokParser Type
-promotedTypeParser = withSpanAnn (TAnn . mkAnnotation) $ do
+promotedTypeParser = withSpanAnn (TAnn . mkSpanAnnotation) $ do
   -- Accept both TkVarSym "'" and TkTHQuoteTick for promoted types
   -- This handles ambiguity between TH value quotes and promoted types
   expectedTok (TkVarSym "'") <|> expectedTok TkTHQuoteTick
@@ -450,7 +450,7 @@
   maybe (fail "promoted type") pure (markTypePromoted ty)
 
 typeParenOperatorParser :: TokParser Type
-typeParenOperatorParser = withSpanAnn (TAnn . mkAnnotation) $ do
+typeParenOperatorParser = withSpanAnn (TAnn . mkSpanAnnotation) $ do
   expectedTok TkSpecialLParen
   starIsType <- isExtensionEnabled StarIsType
   unicodeSyntax <- isExtensionEnabled UnicodeSyntax
@@ -483,7 +483,7 @@
 typeIdentifierParser =
   tokenSatisfy "identifier" $ \tok ->
     identifierName tok <&> \name ->
-      TAnn (mkAnnotation (lexTokenSpan tok)) $
+      TAnn (mkSpanAnnotation (lexTokenSpan tok)) $
         case (nameQualifier name, nameType name, T.uncons (nameText name)) of
           (Nothing, NameVarId, Just (c, _))
             | isLower c || c == '_' ->
@@ -497,8 +497,8 @@
   if starIsType
     then tokenSatisfy "star kind" $ \tok ->
       case lexTokenKind tok of
-        TkVarSym sym | isStarTypeSymbol starIsType unicodeSyntax sym -> Just (TAnn (mkAnnotation (lexTokenSpan tok)) (TStar (lexTokenText tok)))
-        TkConSym sym | isStarTypeSymbol starIsType unicodeSyntax sym -> Just (TAnn (mkAnnotation (lexTokenSpan tok)) (TStar (lexTokenText tok)))
+        TkVarSym sym | isStarTypeSymbol starIsType unicodeSyntax sym -> Just (TAnn (mkSpanAnnotation (lexTokenSpan tok)) (TStar (lexTokenText tok)))
+        TkConSym sym | isStarTypeSymbol starIsType unicodeSyntax sym -> Just (TAnn (mkSpanAnnotation (lexTokenSpan tok)) (TStar (lexTokenText tok)))
         _ -> Nothing
     else MP.empty
 
@@ -507,7 +507,7 @@
   starIsType && (sym == "*" || (unicodeSyntax && sym == "★"))
 
 typeListParser :: TokParser Type
-typeListParser = withSpanAnn (TAnn . mkAnnotation) $ do
+typeListParser = withSpanAnn (TAnn . mkSpanAnnotation) $ do
   expectedTok TkSpecialLBracket
   closedImmediately <- optionalTok TkSpecialRBracket
   if closedImmediately
@@ -518,7 +518,7 @@
       pure (TList Unpromoted elems)
 
 typeParenOrTupleParser :: TokParser Type
-typeParenOrTupleParser = withSpanAnn (TAnn . mkAnnotation) $ do
+typeParenOrTupleParser = withSpanAnn (TAnn . mkSpanAnnotation) $ do
   (tupleFlavor, closeTok) <- tupleDelimsParser
   closedImmediately <- optionalTok closeTok
   if closedImmediately
diff --git a/src/Aihc/Parser/Syntax.hs b/src/Aihc/Parser/Syntax.hs
--- a/src/Aihc/Parser/Syntax.hs
+++ b/src/Aihc/Parser/Syntax.hs
@@ -150,6 +150,7 @@
     instanceHeadName,
     instanceHeadTypes,
     mkAnnotation,
+    mkSpanAnnotation,
     fromAnnotation,
     stripAnnotations,
     peelArithSeqAnn,
@@ -1342,7 +1343,7 @@
 
 -- | A single qualifier inside a guarded right-hand side.
 data GuardQualifier
-  = -- | Metadata for the whole guard qualifier (typically a 'SourceSpan' via 'mkAnnotation').
+  = -- | Metadata for the whole guard qualifier (typically a 'SourceSpan' via 'mkSpanAnnotation').
     GuardAnn Annotation GuardQualifier
   | -- | @x > 0@
     GuardExpr Expr
@@ -1375,7 +1376,7 @@
   deriving (Data, Eq, Show, Generic, NFData)
 
 literalAnnSpan :: SourceSpan -> Literal -> Literal
-literalAnnSpan sp = LitAnn (mkAnnotation sp)
+literalAnnSpan sp = LitAnn (mkSpanAnnotation sp)
 
 peelLiteralAnn :: Literal -> Literal
 peelLiteralAnn (LitAnn _ inner) = peelLiteralAnn inner
@@ -1560,7 +1561,7 @@
   deriving (Data, Eq, Show, Generic, NFData)
 
 typeAnnSpan :: SourceSpan -> Type -> Type
-typeAnnSpan sp = TAnn (mkAnnotation sp)
+typeAnnSpan sp = TAnn (mkSpanAnnotation sp)
 
 -- | Peel nested 'TAnn' wrappers (e.g. span-only dynamic annotations).
 peelTypeAnn :: Type -> Type
@@ -1867,7 +1868,7 @@
 
 -- | A data constructor declaration.
 data DataConDecl
-  = -- | Metadata for the whole constructor declaration (typically a 'SourceSpan' via 'mkAnnotation').
+  = -- | Metadata for the whole constructor declaration (typically a 'SourceSpan' via 'mkSpanAnnotation').
     DataConAnn Annotation DataConDecl
   | -- | @Just a@
     PrefixCon [TyVarBinder] [Type] UnqualifiedName [BangType]
@@ -2035,7 +2036,7 @@
 
 -- | An item inside an instance body.
 data InstanceDeclItem
-  = -- | Metadata for the whole instance item (typically a 'SourceSpan' via 'mkAnnotation').
+  = -- | Metadata for the whole instance item (typically a 'SourceSpan' via 'mkSpanAnnotation').
     InstanceItemAnn Annotation InstanceDeclItem
   | -- | A method body such as @show x = ...@
     InstanceItemBind ValueDecl
@@ -2146,6 +2147,16 @@
     Nothing -> DynamicAnnotation (toDyn value)
 {-# INLINE mkAnnotation #-}
 
+-- | Make an annotation from a 'SourceSpan'.
+--
+-- This gives the same result as @'mkAnnotation' span@, but it does not do a
+-- run-time type test. Thus GHC can build the constructor directly. With
+-- 'mkAnnotation', a lazy field often gets a thunk that keeps its free
+-- variables (for example, the whole token) alive until something forces it.
+mkSpanAnnotation :: SourceSpan -> Annotation
+mkSpanAnnotation = SourceSpanAnnotation
+{-# INLINE mkSpanAnnotation #-}
+
 fromAnnotation :: (Typeable a) => Annotation -> Maybe a
 fromAnnotation (SourceSpanAnnotation span') = cast span'
 fromAnnotation (DynamicAnnotation value) = fromDynamic value
@@ -2320,7 +2331,7 @@
 
 -- | A statement inside a @do@ block.
 data DoStmt body
-  = -- | Metadata for the whole do-statement (typically a 'SourceSpan' via 'mkAnnotation').
+  = -- | Metadata for the whole do-statement (typically a 'SourceSpan' via 'mkSpanAnnotation').
     DoAnn Annotation (DoStmt body)
   | -- | @pat <- expr@
     DoBind Pattern body
@@ -2340,7 +2351,7 @@
 -- Commands mirror expressions but live in a separate namespace so the
 -- pretty-printer knows not to parenthesise @do { … }@ as an infix LHS.
 data Cmd
-  = -- | Metadata for the whole command (typically a 'SourceSpan' via 'mkAnnotation').
+  = -- | Metadata for the whole command (typically a 'SourceSpan' via 'mkSpanAnnotation').
     CmdAnn Annotation Cmd
   | -- | @exp -\< exp@ or @exp -\<\< exp@
     CmdArrApp Expr ArrAppType Expr
@@ -2376,7 +2387,7 @@
 
 -- | A statement inside a list comprehension.
 data CompStmt
-  = -- | Metadata for the whole comprehension statement (typically a 'SourceSpan' via 'mkAnnotation').
+  = -- | Metadata for the whole comprehension statement (typically a 'SourceSpan' via 'mkSpanAnnotation').
     CompAnn Annotation CompStmt
   | -- | @x <- xs@
     CompGen Pattern Expr
@@ -2400,7 +2411,7 @@
 
 -- | An arithmetic sequence expression.
 data ArithSeq
-  = -- | Metadata for the whole arithmetic sequence (typically a 'SourceSpan' via 'mkAnnotation').
+  = -- | Metadata for the whole arithmetic sequence (typically a 'SourceSpan' via 'mkSpanAnnotation').
     ArithSeqAnn Annotation ArithSeq
   | -- | @[expr ..]@
     ArithSeqFrom Expr
