git.iko.soy/rowtype-yoga/purescript-native
on golang help sign in
Add information about skolems to type errors
commit1209e86d3637a96ef01b5b1c86a525b2e933a9f9 authorPhil Freeman <paf31@cantab.net> dateSun 22 Nov 2015, 14:10 (11 years ago) parent5241a99
9 files changed+102−68
src/Language/PureScript/Errors.hs+70−40 src/Language/PureScript/Pretty/Types.hs+1−1 src/Language/PureScript/TypeChecker/Entailment.hs+7−7 src/Language/PureScript/TypeChecker/Kinds.hs+1−1 src/Language/PureScript/TypeChecker/Skolems.hs+8−8 src/Language/PureScript/TypeChecker/Subsumption.hs+1−1 src/Language/PureScript/TypeChecker/Types.hs+5−2 src/Language/PureScript/TypeChecker/Unify.hs+7−7 src/Language/PureScript/Types.hs+2−1
src/Language/PureScript/Errors.hs+70−40
@@ -14,6 +14,7 @@
1414
1515{-# LANGUAGE GeneralizedNewtypeDeriving #-}
1616{-# LANGUAGE FlexibleContexts #-}
17+{-# LANGUAGE RecordWildCards #-}
1718{-# LANGUAGE MultiParamTypeClasses #-}
1819
1920module Language.PureScript.Errors where
@@ -24,7 +25,6 @@ import Prelude.Compat
2425import Data.Ord (comparing)
2526import Data.Either (lefts, rights)
2627import Data.List (intercalate, transpose, nub, nubBy, sortBy)
27−import Data.Function (on)
2828import Data.Foldable (fold)
2929
3030import qualified Data.Map as M
@@ -33,7 +33,7 @@ import Control.Monad
3333import Control.Monad.Writer
3434import Control.Monad.Error.Class (MonadError(..))
3535import Control.Monad.Trans.State.Lazy
36−import Control.Arrow (first, (&&&))
36+import Control.Arrow ((&&&))
3737
3838import Language.PureScript.Crash
3939import Language.PureScript.AST
@@ -311,11 +311,16 @@ onErrorMessages f = MultipleErrors . map f . runMultipleErrors
311311addHint :: ErrorMessageHint -> MultipleErrors -> MultipleErrors
312312addHint hint = onErrorMessages $ \(ErrorMessage hints se) -> ErrorMessage (hint : hints) se
313313
314−-- | The various types of things which might need to be relabelled in errors messages.
315−data LabelType = TypeLabel | SkolemLabel String deriving (Show, Read, Eq, Ord)
316−
317314-- | A map from rigid type variable name/unknown variable pairs to new variables.
318−type UnknownMap = M.Map (LabelType, Int) Int
315+data TypeMap = TypeMap
316+ { umSkolemMap :: M.Map Int (String, Int, Maybe SourceSpan)
317+ , umNextSkolem :: Int
318+ , umUnknownMap :: M.Map Int Int
319+ , umNextUnknown :: Int
320+ } deriving Show
321+
322+defaultUnknownMap :: TypeMap
323+defaultUnknownMap = TypeMap M.empty 0 M.empty 0
319324
320325-- | How critical the issue is
321326data Level = Error | Warning deriving Show
@@ -326,56 +331,80 @@ data Level = Error | Warning deriving Show
326331unwrapErrorMessage :: ErrorMessage -> SimpleErrorMessage
327332unwrapErrorMessage (ErrorMessage _ se) = se
328333
329−replaceUnknowns :: Type -> State UnknownMap Type
334+replaceUnknowns :: Type -> State TypeMap Type
330335replaceUnknowns = everywhereOnTypesM replaceTypes
331336 where
332− lookupTable :: (LabelType, Int) -> UnknownMap -> (Int, UnknownMap)
333− lookupTable x m = case M.lookup x m of
334− Nothing -> let i = length (filter (on (==) fst x) (M.keys m)) in (i, M.insert x i m)
335− Just i -> (i, m)
336−
337− replaceTypes :: Type -> State UnknownMap Type
338− replaceTypes (TUnknown u) = state $ first TUnknown . lookupTable (TypeLabel, u)
339− replaceTypes (Skolem name s sko) = state $ first (flip (Skolem name) sko) . lookupTable (SkolemLabel name, s)
337+ replaceTypes :: Type -> State TypeMap Type
338+ replaceTypes (TUnknown u) = do
339+ m <- get
340+ case M.lookup u (umUnknownMap m) of
341+ Nothing -> do
342+ let u' = umNextUnknown m
343+ put $ m { umUnknownMap = M.insert u u' (umUnknownMap m), umNextUnknown = u' + 1 }
344+ return (TUnknown u')
345+ Just u' -> return (TUnknown u')
346+ replaceTypes (Skolem name s sko ss) = do
347+ m <- get
348+ case M.lookup s (umSkolemMap m) of
349+ Nothing -> do
350+ let s' = umNextSkolem m
351+ put $ m { umSkolemMap = M.insert s (name, s', ss) (umSkolemMap m), umNextSkolem = s' + 1 }
352+ return (Skolem name s' sko ss)
353+ Just (_, s', _) -> return (Skolem name s' sko ss)
340354 replaceTypes other = return other
341355
342356onTypesInErrorMessageM :: (Applicative m) => (Type -> m Type) -> ErrorMessage -> m ErrorMessage
343357onTypesInErrorMessageM f (ErrorMessage hints simple) = ErrorMessage <$> traverse gHint hints <*> gSimple simple
344358 where
345− gSimple (InfiniteType t) = InfiniteType <$> f t
346− gSimple (TypesDoNotUnify t1 t2) = TypesDoNotUnify <$> f t1 <*> f t2
347− gSimple (ConstrainedTypeUnified t1 t2) = ConstrainedTypeUnified <$> f t1 <*> f t2
348− gSimple (ExprDoesNotHaveType e t) = ExprDoesNotHaveType e <$> f t
349− gSimple (CannotApplyFunction t e) = CannotApplyFunction <$> f t <*> pure e
350− gSimple (InvalidInstanceHead t) = InvalidInstanceHead <$> f t
351− gSimple other = pure other
352− gHint (ErrorInSubsumption t1 t2) = ErrorInSubsumption <$> f t1 <*> f t2
353− gHint (ErrorUnifyingTypes t1 t2) = ErrorUnifyingTypes <$> f t1 <*> f t2
354− gHint (ErrorCheckingType e t) = ErrorCheckingType e <$> f t
355− gHint (ErrorCheckingKind t) = ErrorCheckingKind <$> f t
356− gHint (ErrorInApplication e1 t1 e2) = ErrorInApplication e1 <$> f t1 <*> pure e2
357− gHint other = pure other
359+ gSimple (InfiniteType t) = InfiniteType <$> f t
360+ gSimple (TypesDoNotUnify t1 t2) = TypesDoNotUnify <$> f t1 <*> f t2
361+ gSimple (ConstrainedTypeUnified t1 t2) = ConstrainedTypeUnified <$> f t1 <*> f t2
362+ gSimple (ExprDoesNotHaveType e t) = ExprDoesNotHaveType e <$> f t
363+ gSimple (CannotApplyFunction t e) = CannotApplyFunction <$> f t <*> pure e
364+ gSimple (InvalidInstanceHead t) = InvalidInstanceHead <$> f t
365+ gSimple other = pure other
366+
367+ gHint (ErrorInSubsumption t1 t2) = ErrorInSubsumption <$> f t1 <*> f t2
368+ gHint (ErrorUnifyingTypes t1 t2) = ErrorUnifyingTypes <$> f t1 <*> f t2
369+ gHint (ErrorCheckingType e t) = ErrorCheckingType e <$> f t
370+ gHint (ErrorCheckingKind t) = ErrorCheckingKind <$> f t
371+ gHint (ErrorInApplication e1 t1 e2) = ErrorInApplication e1 <$> f t1 <*> pure e2
372+ gHint other = pure other
358373
359374-- |
360375-- Pretty print a single error, simplifying if necessary
361376--
362−prettyPrintSingleError :: Bool -> Level -> ErrorMessage -> State UnknownMap Box.Box
363−prettyPrintSingleError full level e = prettyPrintErrorMessage <$> onTypesInErrorMessageM replaceUnknowns (if full then e else simplifyErrorMessage e)
377+prettyPrintSingleError :: Bool -> Level -> ErrorMessage -> State TypeMap Box.Box
378+prettyPrintSingleError full level e = do
379+ em <- onTypesInErrorMessageM replaceUnknowns (if full then e else simplifyErrorMessage e)
380+ um <- get
381+ return (prettyPrintErrorMessage um em)
364382 where
365383
366384 -- Pretty print an ErrorMessage
367− prettyPrintErrorMessage :: ErrorMessage -> Box.Box
368− prettyPrintErrorMessage (ErrorMessage hints simple) =
385+ prettyPrintErrorMessage :: TypeMap -> ErrorMessage -> Box.Box
386+ prettyPrintErrorMessage typeMap (ErrorMessage hints simple) =
369387 paras $
370− [ foldr renderHint (indent (renderSimpleErrorMessage simple)) hints
371− , Box.moveDown 1 $ paras [ line $ "See " ++ wikiUri ++ " for more information, "
372− , line $ "or to contribute content related to this " ++ levelText ++ "."
373− ]
374− ]
388+ foldr renderHint (indent (renderSimpleErrorMessage simple)) hints
389+ : typeInformation
390+ ++ [ Box.moveDown 1 $ paras [ line $ "See " ++ wikiUri ++ " for more information, "
391+ , line $ "or to contribute content related to this " ++ levelText ++ "."
392+ ]
393+ ]
375394 where
376395 wikiUri :: String
377396 wikiUri = "https://github.com/purescript/purescript/wiki/Error-Code-" ++ errorCode e
378397
398+ typeInformation :: [Box.Box]
399+ typeInformation = M.elems . M.mapMaybe skolemInfo . umSkolemMap $ typeMap
400+ where
401+ skolemInfo :: (String, Int, Maybe SourceSpan) -> Maybe Box.Box
402+ skolemInfo (name, s, Just ss) =
403+ Just . Box.moveDown 1 $ paras [ line $ "(" ++ name ++ show s ++ " is a rigid type variable"
404+ , line $ " bound at " ++ displayStartEndPos ss ++ ")"
405+ ]
406+ skolemInfo _ = Nothing
407+
379408 renderSimpleErrorMessage :: SimpleErrorMessage -> Box.Box
380409 renderSimpleErrorMessage (CannotGetFileInfo path) =
381410 paras [ line "Unable to read file info: "
@@ -878,6 +907,7 @@ prettyPrintSingleError full level e = prettyPrintErrorMessage <$> onTypesInError
878907 hintCategory ErrorUnifyingTypes{} = CheckHint
879908 hintCategory ErrorInSubsumption{} = CheckHint
880909 hintCategory ErrorInApplication{} = CheckHint
910+ hintCategory ErrorCheckingKind{} = CheckHint
881911 hintCategory PositionedError{} = PositionHint
882912 hintCategory _ = OtherHint
883913
@@ -895,13 +925,13 @@ prettyPrintMultipleWarnings full = renderBox . prettyPrintMultipleWarningsBox fu
895925
896926-- | Pretty print warnings as a Box
897927prettyPrintMultipleWarningsBox :: Bool -> MultipleErrors -> Box.Box
898−prettyPrintMultipleWarningsBox full = flip evalState M.empty . prettyPrintMultipleErrorsWith Warning "Warning found:" "Warning" full
928+prettyPrintMultipleWarningsBox full = flip evalState defaultUnknownMap . prettyPrintMultipleErrorsWith Warning "Warning found:" "Warning" full
899929
900930-- | Pretty print errors as a Box
901931prettyPrintMultipleErrorsBox :: Bool -> MultipleErrors -> Box.Box
902−prettyPrintMultipleErrorsBox full = flip evalState M.empty . prettyPrintMultipleErrorsWith Error "Error found:" "Error" full
932+prettyPrintMultipleErrorsBox full = flip evalState defaultUnknownMap . prettyPrintMultipleErrorsWith Error "Error found:" "Error" full
903933
904−prettyPrintMultipleErrorsWith :: Level -> String -> String -> Bool -> MultipleErrors -> State UnknownMap Box.Box
934+prettyPrintMultipleErrorsWith :: Level -> String -> String -> Bool -> MultipleErrors -> State TypeMap Box.Box
905935prettyPrintMultipleErrorsWith level intro _ full (MultipleErrors [e]) = do
906936 result <- prettyPrintSingleError full level e
907937 return $
src/Language/PureScript/Pretty/Types.hs+1−1
@@ -45,7 +45,7 @@ typeLiterals = mkPattern match
4545 match (PrettyPrintObject row) = Just $ prettyPrintRowWith '{' '}' row
4646 match (TypeConstructor ctor) = Just $ text $ runProperName $ disqualify ctor
4747 match (TUnknown u) = Just $ text $ '_' : show u
48− match (Skolem name s _) = Just $ text $ name ++ show s
48+ match (Skolem name s _ _) = Just $ text $ name ++ show s
4949 match REmpty = Just $ text "()"
5050 match row@RCons{} = Just $ prettyPrintRowWith '(' ')' row
5151 match _ = Nothing
src/Language/PureScript/TypeChecker/Entailment.hs+7−7
@@ -147,7 +147,7 @@ entails moduleName context = solve
147147-- and return a substitution from type variables to types which makes the type heads unify.
148148--
149149typeHeadsAreEqual :: ModuleName -> Type -> Type -> Maybe [(String, Type)]
150−typeHeadsAreEqual _ (Skolem _ s1 _) (Skolem _ s2 _) | s1 == s2 = Just []
150+typeHeadsAreEqual _ (Skolem _ s1 _ _) (Skolem _ s2 _ _) | s1 == s2 = Just []
151151typeHeadsAreEqual _ t (TypeVar v) = Just [(v, t)]
152152typeHeadsAreEqual _ (TypeConstructor c1) (TypeConstructor c2) | c1 == c2 = Just []
153153typeHeadsAreEqual m (TypeApp h1 t1) (TypeApp h2 t2) = (++) <$> typeHeadsAreEqual m h1 h2
@@ -164,12 +164,12 @@ typeHeadsAreEqual m r1@RCons{} r2@RCons{} =
164164 <*> go sd1 r1' sd2 r2'
165165 where
166166 go :: [(String, Type)] -> Type -> [(String, Type)] -> Type -> Maybe [(String, Type)]
167− go [] REmpty [] REmpty = Just []
168− go [] (TUnknown _) _ _ = Just []
169− go [] (TypeVar v1) [] (TypeVar v2) | v1 == v2 = Just []
170− go [] (Skolem _ s1 _) [] (Skolem _ s2 _) | s1 == s2 = Just []
171− go sd r [] (TypeVar v) = Just [(v, rowFromList (sd, r))]
172− go _ _ _ _ = Nothing
167+ go [] REmpty [] REmpty = Just []
168+ go [] (TUnknown _) _ _ = Just []
169+ go [] (TypeVar v1) [] (TypeVar v2) | v1 == v2 = Just []
170+ go [] (Skolem _ s1 _ _) [] (Skolem _ s2 _ _) | s1 == s2 = Just []
171+ go sd r [] (TypeVar v) = Just [(v, rowFromList (sd, r))]
172+ go _ _ _ _ = Nothing
173173typeHeadsAreEqual _ _ _ = Nothing
174174
175175-- |
src/Language/PureScript/TypeChecker/Kinds.hs+1−1
@@ -226,7 +226,7 @@ infer' other = (, []) <$> go other
226226 go (TypeVar v) = do
227227 Just moduleName <- checkCurrentModule <$> get
228228 lookupTypeVariable moduleName (Qualified Nothing (ProperName v))
229− go (Skolem v _ _) = do
229+ go (Skolem v _ _ _) = do
230230 Just moduleName <- checkCurrentModule <$> get
231231 lookupTypeVariable moduleName (Qualified Nothing (ProperName v))
232232 go (TypeConstructor v) = do
src/Language/PureScript/TypeChecker/Skolems.hs+8−8
@@ -71,30 +71,30 @@ newSkolemScope = do
7171-- |
7272-- Skolemize a type variable by replacing its instances with fresh skolem constants
7373--
74−skolemize :: String -> Int -> SkolemScope -> Type -> Type
75−skolemize ident sko scope = replaceTypeVars ident (Skolem ident sko scope)
74+skolemize :: String -> Int -> SkolemScope -> Maybe SourceSpan -> Type -> Type
75+skolemize ident sko scope ss = replaceTypeVars ident (Skolem ident sko scope ss)
7676
7777-- |
7878-- This function has one purpose - to skolemize type variables appearing in a
7979-- SuperClassDictionary placeholder. These type variables are somewhat unique since they are the
8080-- only example of scoped type variables.
8181--
82−skolemizeTypesInValue :: String -> Int -> SkolemScope -> Expr -> Expr
83−skolemizeTypesInValue ident sko scope =
82+skolemizeTypesInValue :: String -> Int -> SkolemScope -> Maybe SourceSpan -> Expr -> Expr
83+skolemizeTypesInValue ident sko scope ss =
8484 let
8585 (_, f, _, _, _) = everywhereWithContextOnValuesM [] defS onExpr onBinder defS defS
8686 in runIdentity . f
8787 where
8888 onExpr :: [String] -> Expr -> Identity ([String], Expr)
8989 onExpr sco (SuperClassDictionary c ts)
90− | ident `notElem` sco = return (sco, SuperClassDictionary c (map (skolemize ident sko scope) ts))
90+ | ident `notElem` sco = return (sco, SuperClassDictionary c (map (skolemize ident sko scope ss) ts))
9191 onExpr sco (TypedValue check val ty)
92− | ident `notElem` sco = return (sco ++ peelTypeVars ty, TypedValue check val (skolemize ident sko scope ty))
92+ | ident `notElem` sco = return (sco ++ peelTypeVars ty, TypedValue check val (skolemize ident sko scope ss ty))
9393 onExpr sco other = return (sco, other)
9494
9595 onBinder :: [String] -> Binder -> Identity ([String], Binder)
9696 onBinder sco (TypedBinder ty b)
97− | ident `notElem` sco = return (sco ++ peelTypeVars ty, TypedBinder (skolemize ident sko scope ty) b)
97+ | ident `notElem` sco = return (sco ++ peelTypeVars ty, TypedBinder (skolemize ident sko scope ss ty) b)
9898 onBinder sco other = return (sco, other)
9999
100100 peelTypeVars :: Type -> [String]
@@ -129,7 +129,7 @@ skolemEscapeCheck root@TypedValue{} =
129129 collectSkolems :: Type -> [SkolemScope]
130130 collectSkolems = nub . everythingOnTypes (++) collect
131131 where
132− collect (Skolem _ _ scope) = [scope]
132+ collect (Skolem _ _ scope _) = [scope]
133133 collect _ = []
134134 go scos _ = (scos, [])
135135 findBindingScope :: SkolemScope -> Maybe Expr
src/Language/PureScript/TypeChecker/Subsumption.hs+1−1
@@ -55,7 +55,7 @@ subsumes' val ty1 (ForAll ident ty2 sco) =
5555 case sco of
5656 Just sco' -> do
5757 sko <- newSkolemConstant
58− let sk = skolemize ident sko sco' ty2
58+ let sk = skolemize ident sko sco' Nothing ty2
5959 subsumes val ty1 sk
6060 Nothing -> internalError "subsumes: unspecified skolem scope"
6161subsumes' val (TypeApp (TypeApp f1 arg1) ret1) (TypeApp (TypeApp f2 arg2) ret2) | f1 == tyFunction && f2 == tyFunction = do
src/Language/PureScript/TypeChecker/Types.hs+5−2
@@ -487,8 +487,11 @@ check' :: forall m.
487487check' val (ForAll ident ty _) = do
488488 scope <- newSkolemScope
489489 sko <- newSkolemConstant
490− let sk = skolemize ident sko scope ty
491− let skVal = skolemizeTypesInValue ident sko scope val
490+ let ss = case val of
491+ PositionedValue pos _ _ -> Just pos
492+ _ -> Nothing
493+ sk = skolemize ident sko scope ss ty
494+ skVal = skolemizeTypesInValue ident sko scope ss val
492495 val' <- check skVal sk
493496 return $ TypedValue True val' (ForAll ident ty (Just scope))
494497check' val t@(ConstrainedType constraints ty) = do
src/Language/PureScript/TypeChecker/Unify.hs+7−7
@@ -105,13 +105,13 @@ unifyTypes t1 t2 = do
105105 case (sc1, sc2) of
106106 (Just sc1', Just sc2') -> do
107107 sko <- newSkolemConstant
108− let sk1 = skolemize ident1 sko sc1' ty1
109− let sk2 = skolemize ident2 sko sc2' ty2
108+ let sk1 = skolemize ident1 sko sc1' Nothing ty1
109+ let sk2 = skolemize ident2 sko sc2' Nothing ty2
110110 sk1 `unifyTypes` sk2
111111 _ -> internalError "unifyTypes: unspecified skolem scope"
112112 unifyTypes' (ForAll ident ty1 (Just sc)) ty2 = do
113113 sko <- newSkolemConstant
114− let sk = skolemize ident sko sc ty1
114+ let sk = skolemize ident sko sc Nothing ty1
115115 sk `unifyTypes` ty2
116116 unifyTypes' ForAll{} _ = internalError "unifyTypes: unspecified skolem scope"
117117 unifyTypes' ty f@ForAll{} = f `unifyTypes` ty
@@ -121,7 +121,7 @@ unifyTypes t1 t2 = do
121121 unifyTypes' (TypeApp t3 t4) (TypeApp t5 t6) = do
122122 t3 `unifyTypes` t5
123123 t4 `unifyTypes` t6
124− unifyTypes' (Skolem _ s1 _) (Skolem _ s2 _) | s1 == s2 = return ()
124+ unifyTypes' (Skolem _ s1 _ _) (Skolem _ s2 _ _) | s1 == s2 = return ()
125125 unifyTypes' (KindedType ty1 _) ty2 = ty1 `unifyTypes` ty2
126126 unifyTypes' ty1 (KindedType ty2 _) = ty1 `unifyTypes` ty2
127127 unifyTypes' r1@RCons{} r2 = unifyRows r1 r2
@@ -162,7 +162,7 @@ unifyRows r1 r2 =
162162 solveType u2 (rowFromList (sd1, rest))
163163 unifyRows' [] REmpty [] REmpty = return ()
164164 unifyRows' [] (TypeVar v1) [] (TypeVar v2) | v1 == v2 = return ()
165− unifyRows' [] (Skolem _ s1 _) [] (Skolem _ s2 _) | s1 == s2 = return ()
165+ unifyRows' [] (Skolem _ s1 _ _) [] (Skolem _ s2 _ _) | s1 == s2 = return ()
166166 unifyRows' _ _ _ _ = throwError . errorMessage $ TypesDoNotUnify r1 r2
167167
168168-- |
@@ -170,7 +170,7 @@ unifyRows r1 r2 =
170170--
171171unifiesWith :: Type -> Type -> Bool
172172unifiesWith (TUnknown u1) (TUnknown u2) | u1 == u2 = True
173−unifiesWith (Skolem _ s1 _) (Skolem _ s2 _) | s1 == s2 = True
173+unifiesWith (Skolem _ s1 _ _) (Skolem _ s2 _ _) | s1 == s2 = True
174174unifiesWith (TypeVar v1) (TypeVar v2) | v1 == v2 = True
175175unifiesWith (TypeConstructor c1) (TypeConstructor c2) | c1 == c2 = True
176176unifiesWith (TypeApp h1 t1) (TypeApp h2 t2) = h1 `unifiesWith` h2 && t1 `unifiesWith` t2
@@ -187,7 +187,7 @@ unifiesWith r1@RCons{} r2@RCons{} =
187187 go :: [(String, Type)] -> Type -> [(String, Type)] -> Type -> Bool
188188 go [] REmpty [] REmpty = True
189189 go [] (TypeVar v1) [] (TypeVar v2) = v1 == v2
190− go [] (Skolem _ s1 _) [] (Skolem _ s2 _) = s1 == s2
190+ go [] (Skolem _ s1 _ _) [] (Skolem _ s2 _ _) = s1 == s2
191191 go [] (TUnknown _) _ _ = True
192192 go _ _ [] (TUnknown _) = True
193193 go _ (TUnknown _) _ (TUnknown _) = True
src/Language/PureScript/Types.hs+2−1
@@ -34,6 +34,7 @@ import Control.Monad ((<=<))
3434import Language.PureScript.Names
3535import Language.PureScript.Kinds
3636import Language.PureScript.Traversals
37+import Language.PureScript.AST.SourcePos
3738
3839-- |
3940-- An identifier for the scope of a skolem variable
@@ -75,7 +76,7 @@ data Type
7576 -- |
7677 -- A skolem constant
7778 --
78− | Skolem String Int SkolemScope
79+ | Skolem String Int SkolemScope (Maybe SourceSpan)
7980 -- |
8081 -- An empty row
8182 --