Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug] Fix 'isNormalType' and add 'prop_normalizedTypeIsNormal' #6272

Merged
merged 1 commit into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Fixed

- In #6272 fixed a bug in `isNormalType`.
42 changes: 31 additions & 11 deletions plutus-core/plutus-core/src/PlutusCore/Check/Normal.hs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}

-- | This module makes sure types are normalized inside programs.
Expand All @@ -14,22 +15,26 @@ import PlutusPrelude

import PlutusCore.Core
import PlutusCore.Error
import PlutusCore.MkPlc (mkTyBuiltinOf)

import Control.Monad.Except
import Universe.Core (HasUniApply (matchUniApply), SomeTypeIn (..))

-- | Ensure that all types in the 'Program' are normalized.
checkProgram
:: (AsNormCheckError e tyname name uni fun ann, MonadError e m)
:: (AsNormCheckError e tyname name uni fun ann, HasUniApply uni, MonadError e m)
=> Program tyname name uni fun ann -> m ()
checkProgram (Program _ _ t) = checkTerm t

-- | Ensure that all types in the 'Term' are normalized.
checkTerm
:: (AsNormCheckError e tyname name uni fun ann, MonadError e m)
:: (AsNormCheckError e tyname name uni fun ann, HasUniApply uni, MonadError e m)
=> Term tyname name uni fun ann -> m ()
checkTerm p = throwingEither _NormCheckError $ check p

check :: Term tyname name uni fun ann -> Either (NormCheckError tyname name uni fun ann) ()
check
:: HasUniApply uni
=> Term tyname name uni fun ann -> Either (NormCheckError tyname name uni fun ann) ()
check (Error _ ty) = normalType ty
check (TyInst _ t ty) = check t >> normalType ty
check (IWrap _ pat arg term) = normalType pat >> normalType arg >> check term
Expand All @@ -43,20 +48,35 @@ check Var{} = pure ()
check Constant{} = pure ()
check Builtin{} = pure ()

isNormalType :: Type tyname uni ann -> Bool
isNormalType :: HasUniApply uni => Type tyname uni ann -> Bool
isNormalType = isRight . normalType

normalType :: Type tyname uni ann -> Either (NormCheckError tyname name uni fun ann) ()
normalType
:: HasUniApply uni
=> Type tyname uni ann -> Either (NormCheckError tyname name uni fun ann) ()
normalType (TyFun _ i o) = normalType i >> normalType o
normalType (TyForall _ _ _ ty) = normalType ty
normalType (TyIFix _ pat arg) = normalType pat >> normalType arg
normalType (TySOP _ tyls) = traverse_ (traverse_ normalType) tyls
normalType (TyLam _ _ _ ty) = normalType ty
-- See Note [PLC types and universes].
normalType TyBuiltin{} = pure ()
normalType ty = neutralType ty

neutralType :: Type tyname uni ann -> Either (NormCheckError tyname name uni fun ann) ()
neutralType TyVar{} = pure ()
neutralType (TyApp _ ty1 ty2) = neutralType ty1 >> normalType ty2
neutralType ty = Left (BadType (typeAnn ty) ty "neutral type")
neutralType
:: HasUniApply uni
=> Type tyname uni ann -> Either (NormCheckError tyname name uni fun ann) ()
neutralType TyVar{} = pure ()
neutralType (TyBuiltin ann someUni) = neutralUni ann someUni
neutralType (TyApp _ ty1 ty2) = neutralType ty1 >> normalType ty2
neutralType ty = Left (BadType (typeAnn ty) ty "neutral type")

-- See Note [Normalization of built-in types].
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Note:

{- Note [Normalization of built-in types]
Instantiating a polymorphic built-in type amounts to applying it to some arguments. However,
the notion of "applying" is ambiguous, it can mean one of these two things:

1. lifting the built-in type to 'Type' and applying that via 'TyApp'
2. applying the built-in type right inside the universe to get a monomorphized type tag
   (e.g. the default universe has 'DefaultUniApply' for that purpose)

We need both of these things. The former allows us to assign types to polymorphic built-in functions
(otherwise applying a built-in type to a type variable would be unrepresentable), the latter is
used at runtime to juggle type tags so that we can avoid @unsafeCoerce@-ing, bring instances in
scope via 'bring' etc -- for all of that we have to have fully monomorphized type tags at runtime.

So in order for type checking to work we need to normalize polymorphic built-in types. For that
we simply turn intra-universe applications into regular type applications during type normalization.

We could go the other way around and "reduce" regular type applications into intra-universe ones,
however that would be harder to implement, because collapsing a general 'Type' into a 'SomeTypeIn'
is harder than expanding a 'SomeTypeIn' into a 'Type'. And it would also be impossible to do in the
general case, 'cause you can't collapse an application of a built-in type to, say, a type variable
into an intra-universe application as there are no type variables there. I guess we could "reduce"
application of built-in types in some cases and not reduce them in others and still make the whole
thing work, but that requires substantially more logic and is also a lot harder to get right.
Hence we do the opposite, which is straightforward.
-}

neutralUni
:: HasUniApply uni
=> ann -> SomeTypeIn uni -> Either (NormCheckError tyname name uni fun ann) ()
neutralUni ann (SomeTypeIn uni) =
matchUniApply
uni
-- If @uni@ is not an intra-universe application, then it's neutral.
(Right ())
-- If it is, then it's not neutral and we throw an error.
(\_ _ -> Left (BadType ann (mkTyBuiltinOf ann uni) "neutral type"))
4 changes: 2 additions & 2 deletions plutus-core/plutus-core/src/PlutusCore/Core/Instance/Eq.hs
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,9 @@ eqTypeM (TyFun ann1 dom1 cod1) (TyFun ann2 dom2 cod2) = do
eqM ann1 ann2
eqTypeM dom1 dom2
eqTypeM cod1 cod2
eqTypeM (TyBuiltin ann1 bi1) (TyBuiltin ann2 bi2) = do
eqTypeM (TyBuiltin ann1 someUni1) (TyBuiltin ann2 someUni2) = do
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've also changed some naming and formatting for consistency.

eqM ann1 ann2
eqM bi1 bi2
eqM someUni1 someUni2
eqTypeM (TySOP ann1 tyls1) (TySOP ann2 tyls2) = do
eqM ann1 ann2
case zipExact tyls1 tyls2 of
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import Universe

instance Pretty ann => PrettyBy (PrettyConfigClassic configName) (Kind ann) where
prettyBy config = \case
Type ann ->
Type ann ->
parens (sep (consAnnIf config ann
["type"]))
KindArrow ann k k' ->
Expand All @@ -34,13 +34,13 @@ instance Pretty ann => PrettyBy (PrettyConfigClassic configName) (Kind ann) wher
instance (PrettyClassicBy configName tyname, PrettyParens (SomeTypeIn uni), Pretty ann) =>
PrettyBy (PrettyConfigClassic configName) (Type tyname uni ann) where
prettyBy config = \case
TyApp ann t t' ->
TyApp ann t t' ->
brackets' (sep (consAnnIf config ann
[prettyBy config t, prettyBy config t']))
TyVar ann n ->
TyVar ann n ->
sep (consAnnIf config ann
[prettyBy config n])
TyFun ann t t' ->
TyFun ann t t' ->
sexp "fun" (consAnnIf config ann
[prettyBy config t, prettyBy config t'])
TyIFix ann pat arg ->
Expand All @@ -49,12 +49,12 @@ instance (PrettyClassicBy configName tyname, PrettyParens (SomeTypeIn uni), Pret
TyForall ann n k t ->
sexp "all" (consAnnIf config ann
[prettyBy config n, prettyBy config k, prettyBy config t])
TyBuiltin ann n ->
sexp "con" (consAnnIf config ann [prettyBy juxtRenderContext n])
TyLam ann n k t ->
TyBuiltin ann someUni ->
sexp "con" (consAnnIf config ann [prettyBy juxtRenderContext someUni])
TyLam ann n k t ->
sexp "lam" (consAnnIf config ann
[prettyBy config n, prettyBy config k, prettyBy config t])
TySOP ann tyls ->
TySOP ann tyls ->
sexp "sop" (consAnnIf config ann (fmap prettyTyl tyls))
where
prettyTyl tyl = brackets (sep (fmap (prettyBy config) tyl))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ instance (PrettyReadableBy configName tyname, PrettyParens (SomeTypeIn uni)) =>
TyIFix _ pat arg -> iterAppDocM $ \_ prettyArg -> "ifix" :| map prettyArg [pat, arg]
(viewTyForall -> Just (args, body)) -> iterTyForallPrettyM args body
TyForall {} -> error "Panic: 'TyForall' is not covered by 'viewTyForall'"
TyBuiltin _ builtin -> lmap _pcrRenderContext $ prettyM builtin
TyBuiltin _ someUni -> lmap _pcrRenderContext $ prettyM someUni
(viewTyLam -> Just (args, body)) -> iterLamAbsPrettyM args body
TyLam {} -> error "Panic: 'TyLam' is not covered by 'viewTyLam'"
TySOP _ tls -> iterAppDocM $ \_ prettyArg -> "sop" :| fmap prettyArg tls
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ instance tyname ~ TyName => EstablishScoping (Type tyname uni) where
establishScoping (TyVar _ nameDup) = do
name <- freshenTyName nameDup
pure $ TyVar (registerFree name) name
establishScoping (TyBuiltin _ fun) = pure $ TyBuiltin NotAName fun
establishScoping (TyBuiltin _ someUni) = pure $ TyBuiltin NotAName someUni
establishScoping (TySOP _ tyls) =
TySOP NotAName <$> (traverse . traverse) establishScoping tyls

Expand Down
4 changes: 2 additions & 2 deletions plutus-core/plutus-core/src/PlutusCore/DeBruijn.hs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ deBruijnTyWithM h = go
TyIFix ann pat arg -> TyIFix ann <$> go pat <*> go arg
TySOP ann tyls -> TySOP ann <$> (traverse . traverse) go tyls
-- boring non-recursive cases
TyBuiltin ann con -> pure $ TyBuiltin ann con
TyBuiltin ann someUni -> pure $ TyBuiltin ann someUni

deBruijnTermWithM
:: forall m uni fun ann. (MonadReader LevelInfo m)
Expand Down Expand Up @@ -207,7 +207,7 @@ unDeBruijnTyWithM h = go
TyIFix ann pat arg -> TyIFix ann <$> go pat <*> go arg
TySOP ann tyls -> TySOP ann <$> (traverse . traverse) go tyls
-- boring non-recursive cases
TyBuiltin ann con -> pure $ TyBuiltin ann con
TyBuiltin ann someUni -> pure $ TyBuiltin ann someUni

-- | Takes a "handler" function to execute when encountering free variables.
unDeBruijnTermWithM
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ Hence we do the opposite, which is straightforward.
{- | Normalize a built-in type by replacing each application inside the universe with regular
type application.
-}
normalizeUni :: forall k (a :: k) uni tyname. (HasUniApply uni) => uni (Esc a) -> Type tyname uni ()
normalizeUni :: forall k (a :: k) uni tyname. HasUniApply uni => uni (Esc a) -> Type tyname uni ()
normalizeUni uni =
matchUniApply
uni
Expand Down
2 changes: 1 addition & 1 deletion plutus-core/plutus-core/test/Check/Spec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ normalTypes = runQuote $ do
normal = integer
nonNormal = TyApp () (TyLam () aN (Type ()) neutral) normal
pure $ testGroup "normal types" [
testCase "var" $ Normal.isNormalType neutral @?= True
testCase "var" $ Normal.isNormalType @DefaultUni neutral @?= True

, testCase "funNormal" $ Normal.isNormalType (TyFun () normal normal) @?= True
, testCase "funNotNormal" $ Normal.isNormalType (TyFun () normal nonNormal) @?= False
Expand Down
3 changes: 2 additions & 1 deletion plutus-core/plutus-core/test/Normalization/Check.hs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}

module Normalization.Check ( test_normalizationCheck ) where

Expand All @@ -16,7 +17,7 @@ test_applyToValue =
(KindArrow () (Type ()) (Type ()))
(TyApp () datVar aVar)
)
in isNormalType ty @?= True
in isNormalType @DefaultUni ty @?= True

where recVar = TyVar () (TyName (Name "rec" (Unique 0)))
datVar = TyVar () datName
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@

module PlutusCore.Generators.QuickCheck.TypesTests where

import PlutusCore.Check.Normal
import PlutusCore.Core
import PlutusCore.Generators.QuickCheck
import PlutusCore.Normalize
import PlutusCore.Quote

import Control.Monad
import Data.Bifunctor
import Data.Either
import Data.Map.Strict qualified as Map
Expand Down Expand Up @@ -64,3 +69,10 @@ prop_fixKind = withMaxSuccess 30000 $
| k' <- shrink k
, let ty' = fixKind ctx ty k'
]

-- | Check that 'normalizeType' returns a normal type.
prop_normalizedTypeIsNormal :: Property
prop_normalizedTypeIsNormal = withMaxSuccess 10000 $
forAllDoc "k,ty" genKindAndType (shrinkKindAndType Map.empty) $ \ (_, ty) ->
unless (isNormalType . unNormalized . runQuote $ normalizeType ty) $
Left "'normalizeType' returned a non-normal type"
14 changes: 7 additions & 7 deletions plutus-core/testlib/PlutusCore/Generators/NEAT/Type.hs
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,13 @@ ext _ FZ = FZ
ext f (FS x) = FS (f x)

ren :: (m -> n) -> TypeG m -> TypeG n
ren f (TyVarG x) = TyVarG (f x)
ren f (TyFunG ty1 ty2) = TyFunG (ren f ty1) (ren f ty2)
ren f (TyIFixG ty1 k ty2) = TyIFixG (ren f ty1) k (ren f ty2)
ren f (TyForallG k ty) = TyForallG k (ren (ext f) ty)
ren _ (TyBuiltinG b) = TyBuiltinG b
ren f (TyLamG ty) = TyLamG (ren (ext f) ty)
ren f (TyAppG ty1 ty2 k) = TyAppG (ren f ty1) (ren f ty2) k
ren f (TyVarG x) = TyVarG (f x)
ren f (TyFunG ty1 ty2) = TyFunG (ren f ty1) (ren f ty2)
ren f (TyIFixG ty1 k ty2) = TyIFixG (ren f ty1) k (ren f ty2)
ren f (TyForallG k ty) = TyForallG k (ren (ext f) ty)
ren _ (TyBuiltinG someUni) = TyBuiltinG someUni
ren f (TyLamG ty) = TyLamG (ren (ext f) ty)
ren f (TyAppG ty1 ty2 k) = TyAppG (ren f ty1) (ren f ty2) k

exts :: (n -> TypeG m) -> S n -> TypeG (S m)
exts _ FZ = TyVarG FZ
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,9 +235,9 @@ shrinkKindAndType ctx (k0, ty) =
| b' <- shrinkType (Map.insert x ka ctx) b
]
]
TyBuiltin _ builtin ->
TyBuiltin _ someUni ->
[ (kindOfBuiltinType uni', TyBuiltin () $ SomeTypeIn uni')
| SomeTypeIn uni' <- shrinkBuiltinType builtin
| SomeTypeIn uni' <- shrinkBuiltinType someUni
]
TyIFix _ pat arg -> map (Type (), ) $ concat
[ [ fixKind ctx pat $ Type ()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ inhabitType ty0 = local (\ e -> e { geTerms = mempty }) $ do
LamAbs () x a <$> mapExceptT (bindTmName x a) (findTm b)
TyForall _ x k b -> do
TyAbs () x k <$> mapExceptT (bindTyName x k) (findTm b)
TyBuiltin _ b -> lift $ genConstant b
TyBuiltin _ someUni -> lift $ genConstant someUni
-- If we have a type-function application
(viewApp [] -> (f, _)) ->
case f of
Expand Down Expand Up @@ -292,9 +292,9 @@ genTerm mty = checkInvariants $ do
canConst (Just _) = False

genConst Nothing = do
b <- deliver . liftGen . genBuiltinTypeOf $ Type ()
(TyBuiltin () b, ) <$> genConstant b
genConst (Just ty@(TyBuiltin _ b)) = (ty,) <$> genConstant b
someUni <- deliver . liftGen . genBuiltinTypeOf $ Type ()
(TyBuiltin () someUni, ) <$> genConstant someUni
genConst (Just ty@(TyBuiltin _ someUni)) = (ty,) <$> genConstant someUni
genConst _ = error "genConst: impossible"

genDatLet mty = do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ findHelp ctx =
mkHelp :: Map Name (Type TyName DefaultUni ())
-> Type TyName DefaultUni ()
-> Term TyName Name DefaultUni DefaultFun ()
mkHelp _ (TyBuiltin _ b) = minimalBuiltin b
mkHelp _ (TyBuiltin _ someUni) = minimalBuiltin someUni
mkHelp (findHelp -> Just help) ty = TyInst () (Var () help) ty
mkHelp _ ty = Error () ty

Expand All @@ -90,7 +90,7 @@ fixupTerm_ tyctxOld ctxOld tyctxNew ctxNew tyNew tm0 =
Apply _ (Apply _ (TyInst _ (Builtin _ Trace) _) s) tm ->
let (ty', tm') = fixupTerm_ tyctxOld ctxOld tyctxNew ctxNew tyNew tm
in (ty', Apply () (Apply () (TyInst () (Builtin () Trace) ty') s) tm')
_ | TyBuiltin _ b <- tyNew -> (tyNew, minimalBuiltin b)
_ | TyBuiltin _ someUni <- tyNew -> (tyNew, minimalBuiltin someUni)
| otherwise -> (tyNew, mkHelp ctxNew tyNew)
Right ty -> (ty, tm0)

Expand Down
2 changes: 1 addition & 1 deletion plutus-metatheory/src/Raw.hs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ convT (TyApp _ _A _B) = RTyApp (convT _A) (convT _B)
convT (TyBuiltin ann (SomeTypeIn (DefaultUniApply f x))) =
RTyApp (convT (TyBuiltin ann (SomeTypeIn f)))
(convT (TyBuiltin ann (SomeTypeIn x)))
convT (TyBuiltin _ b) = convTyCon b
convT (TyBuiltin _ someUni) = convTyCon someUni
convT (TyIFix _ a b) = RTyMu (convT a) (convT b)
convT (TySOP _ xss) = RTySOP (map (map convT) xss)

Expand Down