rzk-0.11.4: test/Rzk/SchematicSpec.hs
{-# LANGUAGE OverloadedStrings #-}
module Rzk.SchematicSpec (spec) where
import qualified Data.Text as T
import qualified Language.Rzk.Syntax as Syntax
import Rzk.Diagnostic (checkWarningTag, typeErrorTagInScopedContext)
import Rzk.TypeCheck
import Test.Hspec
check :: Maybe RSTTSafeMode -> [T.Text] -> ([String], [String])
check mode sources = case traverse Syntax.parseModule sources of
Left err -> error (T.unpack err)
Right modules -> case checkedModules (zip (map show [1 :: Int ..]) modules)
(emptyContext { ctxRSTTSafeOverride = mode, ctxVerbosity = Silent }) of
Left err -> ([typeErrorTagInScopedContext err], [])
Right (checked, _) ->
(map typeErrorTagInScopedContext (checkedErrors checked),
map checkWarningTag (checkedWarnings checked))
source :: [T.Text] -> T.Text
source declarations = T.unlines ("#lang rzk-1" : declarations)
identity :: T.Text
identity = "#def id (A : U) (a : A) : A := a"
accepts :: [T.Text] -> Expectation
accepts ds = check (Just RSTTSafeError) [source ds] `shouldBe` ([], [])
rejects :: [T.Text] -> Expectation
rejects ds = do
let (errors, warnings) = check (Just RSTTSafeError) [source ds]
errors `shouldBe` ["TypeErrorRSTT"]
warnings `shouldSatisfy` elem "RSTTSchematicWarning"
spec :: Spec
spec = describe "Schematic declarations" $ do
it "accepts ordinary type and family arguments" $ accepts
[ identity
, "#def apply (A : U) (B : A → U) (f : (a : A) → B a) (a : A) : B a := f a"
, "#def good : Unit := apply Unit (\\ x → Unit) (\\ x → id Unit x) unit"
]
it "keeps schematic statements distinct from proofs" $ accepts
[ "#def Bottom : U := (A : U) → A"
, "#postulate trusted : Bottom"
]
it "accepts schematic rule aliases and their instances" $ accepts
[ "#def Rule : U := (A : U) → A → A"
, "#def apply-rule (rule : Rule) (A : U) (a : A) : A := rule A a"
, "#def good : Unit := apply-rule (\\ A a → a) Unit unit"
]
it "checks every universe instantiation before beta reduction" $
rejects [identity, "#def bad : U := id U Unit"]
it "leaves CUBE and TOPE type arguments to the layer discipline" $
mapM_ (\(arg, value) ->
fst (check (Just RSTTSafeError)
[source [identity, "#def bad : " <> arg <> " := id " <> arg <> " " <> value]])
`shouldBe` ["TypeErrorOther"])
[("CUBE", "2"), ("TOPE", "TOP")]
it "rejects families and rules passed as ordinary types" $
mapM_ (\(ty, value) -> rejects [identity, "#def bad : " <> ty <> " := id (" <> ty <> ") (" <> value <> ")"])
[("U → U", "\\ x → x"), ("(A : U) → A → A", "\\ A a → a")]
it "rejects the predicate-of-predicates step of Hurkens" $ rejects
[ "#def P (A : U) : U := A → U"
, "#def bad (X : U) : U := P (P X)"
]
it "checks family arguments pointwise" $ rejects
[ "#def receive (F : Unit → U) : U := F unit"
, "#def bad : U := receive (\\ x → U)"
]
it "checks higher-order family arguments pointwise" $ rejects
[ "#def receive (F : U → U) : U := F Unit"
, "#def bad : U := receive (\\ A → U)"
]
it "allows higher-order meta functions with object-valued instances" $ accepts
[ "#def receive (F : U → U) : U := F Unit"
, "#def good : U := receive (\\ A → A → A)"
]
it "allows meta computations through pairs" $ accepts
[ "#def T : U := first (Unit, unit)"
, "#def good : T := unit"
]
it "rejects a product containing schematic data as an object type" $ rejects
[ identity
, "#def bad : Σ (A : U), A := id (Σ (A : U), A) (Unit, unit)"
]
it "rejects object equality between types" $ rejects
[ "#postulate bad : Unit =_{U} Unit" ]
it "rejects equality elimination into the universe" $ rejects
[ "#def bad : U := idJ (Unit, unit, (\\ _ _ → U), Unit, unit, refl)" ]
it "rechecks used dependencies introduced with safe mode off" $ do
let ds = [ "#set-option \"rstt-safe\" = \"off\"", identity
, "#def bad : U := id U Unit"
, "#set-option \"rstt-safe\" = \"error\""
, "#def use : U := bad" ]
(errors, warnings) = check Nothing [source ds]
errors `shouldBe` ["TypeErrorRSTT"]
warnings `shouldSatisfy` elem "RSTTSchematicWarning"
it "does not recheck unused dependencies" $
check Nothing [source
[ "#set-option \"rstt-safe\" = \"off\"", identity
, "#def bad : U := id U Unit"
, "#set-option \"rstt-safe\" = \"error\""
, "#def good : Unit := unit" ]] `shouldBe` ([], [])
it "keeps the additional check off in ordinary mode" $
check (Just RSTTSafeOff) [source [identity, "#def bad : U := id U Unit"]]
`shouldBe` ([], [])
it "reports kind confusion in warning mode" $
check (Just RSTTSafeWarn) [source [identity, "#def bad : U := id U Unit"]]
`shouldBe` ([], ["RSTTSchematicWarning"])
it "checks standalone commands" $
mapM_ (\command -> rejects [identity, command])
["#check id U Unit : U", "#compute id U Unit", "#compute-nf id U Unit"]
it "preserves valid section abstraction and checks later instances" $ do
let section = ["#section S", "#assume A : U", "#def local-id (a : A) : A := a", "#end S"]
accepts (section <> ["#def good : Unit := local-id Unit unit"])
rejects (section <> ["#def bad : U := local-id U Unit"])
it "reconstructs applications after section parameters become explicit" $ accepts
[ "#def product (X Y : U) : U := Σ (x : X), Y"
, "#section S", "#assume A B : U"
, "#def F (f : A → B) : U := A"
, "#def G (f : A → B) : U := product (F f) (F f)"
, "#end S"
, "#postulate test (A B : U) (f : A → B) : G A B f"
]
it "rejects a universe hidden in a neutral case split" $ rejects
[ "#def mixed (i : 2 | i === 0_2 ∨ i === 1_2) : U :="
, " recOR (i === 0_2 ↦ Unit, i === 1_2 ↦ U)"
, "#postulate receive (A : U) : Unit"
, "#def bad (i : 2 | i === 0_2 ∨ i === 1_2) : Unit := receive (mixed i)"
]
it "accepts object types selected by a neutral case split" $ accepts
[ "#def mixed (i : 2 | i === 0_2 ∨ i === 1_2) : U :="
, " recOR (i === 0_2 ↦ Unit, i === 1_2 ↦ Unit → Unit)"
, "#postulate receive (A : U) : Unit"
, "#def good (i : 2 | i === 0_2 ∨ i === 1_2) : Unit := receive (mixed i)"
]
it "accepts ex falso under an empty shape without following its cyclic annotation" $ accepts
[ "#def empty-shape (A : U) (t : 2 | BOT) : A := recBOT" ]
it "checks erased type arguments in declaration signatures" $ rejects
[ identity, "#postulate bad : id U Unit" ]
it "checks the kinds of boundary values on a universe" $ do
rejects ["#def bad : U [TOP ↦ U] := U"]
accepts ["#def good : U [TOP ↦ Unit] := Unit"]
it "checks actual type arguments after reusing an object family" $ do
let ds =
[ identity
, "#def Family (A : U) : U := A → A"
, "#def good : Family Unit := id (Family Unit) (\\ x → x)"
]
accepts ds
rejects (ds <> ["#def bad : Family U := id (Family U) (\\ x → x)"])
it "checks actual family arguments after reusing an object family" $ do
let ds =
[ identity
, "#def at (F : Unit → U) : U := F unit"
, "#def good : at (\\ _ → Unit) := id (at (\\ _ → Unit)) unit"
]
accepts ds
rejects (ds <> ["#def bad : at (\\ _ → U) := id (at (\\ _ → U)) Unit"])
it "does not generalise an object instance of a mixed-kind family" $ do
let ds =
[ "#def mixed (i : 2 | i === 0_2 ∨ i === 1_2) : U :="
, " recOR (i === 0_2 ↦ Unit, i === 1_2 ↦ U)"
, "#postulate receive (A : U) : Unit"
, "#def good : Unit := receive (mixed 0_2)"
]
accepts ds
rejects (ds <> ["#def bad : Unit := receive (mixed 1_2)"])
it "preserves family checks when section telescopes change" $ do
let ds =
[ identity, "#section S", "#assume A : U", "#assume a : A"
, "#def Family : U := A"
, "#def first-use uses (A) : Family := id Family a"
, "#end S"
, "#def good : Family Unit := id (Family Unit) unit"
]
accepts ds
rejects (ds <> ["#def bad : Family U := id (Family U) Unit"])
it "checks dependencies in arguments erased by reduction" $ do
let ds =
[ "#set-option \"rstt-safe\" = \"off\"", identity
, "#def bad : Unit := id (U → Unit) (\\ _ → unit) Unit"
, "#set-option \"rstt-safe\" = \"error\""
, "#def discard (x : Unit) : Unit := unit"
, "#def use : Unit := discard bad"
]
(errors, warnings) = check Nothing [source ds]
errors `shouldBe` ["TypeErrorRSTT"]
warnings `shouldSatisfy` elem "RSTTSchematicWarning"
it "does not cache a family classification justified by a caller's tope" $ do
let ds =
[ "#def mixed (i : 2 | i === 0_2 ∨ i === 1_2) : U :="
, " recOR (i === 0_2 ↦ Unit, i === 1_2 ↦ U)"
, "#postulate receive (A : U) : Unit"
, "#def good (i : 2 | i === 0_2 ∨ i === 1_2) : Unit :="
, " receive (recOR (i === 0_2 ↦ mixed i, i === 1_2 ↦ Unit))"
]
accepts ds
rejects (ds <>
[ "#def bad (i : 2 | i === 0_2 ∨ i === 1_2) : Unit :="
, " receive (recOR (i === 0_2 ↦ Unit, i === 1_2 ↦ mixed i))"
])