rzk-0.11.4: test/Rzk/RSTTSafeSpec.hs
{-# LANGUAGE OverloadedStrings #-}
module Rzk.RSTTSafeSpec (spec) where
import qualified Data.Text as T
import qualified Language.Rzk.Syntax as Syntax
import Rzk.Diagnostic (checkWarningTag, typeErrorTagInScopedContext)
import Rzk.TypeCheck
import Rzk.TypeCheck.MetaPrefix (isMetaType, metaPrefixOf)
import Language.Rzk.Foil.Syntax (typeAscT, universeT)
import Test.Hspec
-- Exercise the public driver used by the CLI and incremental editor clients.
-- The source fixtures cover the individual fragment conditions.
run :: Maybe RSTTSafeMode -> [T.Text] -> ([String], [String], Int)
run override sources =
let (errors, warnings, holes) = runDetails override sources
in (errors, map checkWarningTag warnings, holes)
runDetails :: Maybe RSTTSafeMode -> [T.Text] -> ([String], [CheckWarning], Int)
runDetails override sources =
case traverse Syntax.parseModule sources of
Left err -> error (T.unpack err)
Right modules -> case checkedModules (zip (map show [1 :: Int ..]) modules)
((allowHoles emptyContext) { ctxRSTTSafeOverride = override, ctxVerbosity = Silent }) of
Left err -> ([typeErrorTagInScopedContext err], [], 0)
Right (checked, holes) ->
( map typeErrorTagInScopedContext (checkedErrors checked)
, checkedWarnings checked
, length holes
)
nested :: T.Text
nested = "#def nested (A : U) (a : A) : A := (\\ (B : U) (b : B) → b) A a\n"
spec :: Spec
spec = describe "RSTT-safe run policy" $ do
it "reports exhausted meta-prefix classification in warn and error modes" $ do
-- A valid type ascription needs a head reduction; force that inspection
-- to reach the judgement limit without an enormous source fixture.
let inspect mode = runTypeCheckWith
(emptyContext { ctxRSTTSafeOverride = Just mode
, ctxVerbosity = Silent
, ctxActionStackDepth = maxActionStackDepth })
(metaPrefixOf (typeAscT universeT universeT))
(warnResult, (_, warnWarnings)) = inspect RSTTSafeWarn
(errorResult, (_, errorWarnings)) = inspect RSTTSafeError
either (const Nothing) Just warnResult `shouldBe` Just 0
map checkWarningTag warnWarnings `shouldBe` ["RSTTIncompleteWarning"]
either (Just . typeErrorTagInScopedContext) (const Nothing) errorResult
`shouldBe` Just "TypeErrorRSTT"
map checkWarningTag errorWarnings `shouldBe` ["RSTTIncompleteWarning"]
it "does not classify an inspection failure as an object type" $ do
let (result, _) = runTypeCheckWith
(emptyContext { ctxVerbosity = Silent
, ctxActionStackDepth = maxActionStackDepth })
(isMetaType (typeAscT universeT universeT))
either (Just . typeErrorTagInScopedContext) (const Nothing) result
`shouldBe` Just "TypeErrorOther"
it "classifies a reducible universe when inspection succeeds" $ do
let (result, (_, warnings)) = runTypeCheckWith
(emptyContext { ctxVerbosity = Silent })
(isMetaType (typeAscT universeT universeT))
either (const Nothing) Just result `shouldBe` Just True
map checkWarningTag warnings `shouldBe` []
it "keeps a CLI error selection despite source attempts to disable checks" $ do
let (errors, _, _) = run (Just RSTTSafeError)
["#lang rzk-1\n#set-option \"rstt-safe\" = \"off\"\n#set-option \"warn-meta-binder\" = \"no\"\n" <> nested]
errors `shouldBe` ["TypeErrorRSTT"]
it "keeps a CLI warning selection despite a source error selection" $ do
let (errors, warnings, _) = run (Just RSTTSafeWarn)
["#lang rzk-1\n#set-option \"rstt-safe\" = \"error\"\n" <> nested]
errors `shouldBe` []
warnings `shouldBe` ["MetaBinderWarning"]
it "applies the CLI selection to subsequent modules" $ do
let (errors, _, _) = run (Just RSTTSafeError)
["#lang rzk-1\n#def id (A : U) (a : A) : A := a\n",
"#lang rzk-1\n#set-option \"rstt-safe\" = \"off\"\n" <> nested]
errors `shouldBe` ["TypeErrorRSTT"]
it "keeps restriction checks enabled by the CLI despite source options" $ do
let source = "#lang rzk-1\n#set-option \"rstt-safe\" = \"off\"\n#set-option \"warn-free-standing-restriction\" = \"no\"\n#postulate restricted (A : U) (a : A) : A [TOP ↦ a]\n"
(errors, _, _) = run (Just RSTTSafeError) [source]
(warnErrors, warnings, _) = run (Just RSTTSafeWarn) [source]
errors `shouldBe` ["TypeErrorRSTT"]
warnErrors `shouldBe` []
warnings `shouldBe` ["FreeStandingRestrictionWarning"]
it "keeps outer-point dependency checks enabled by the CLI" $ do
let source = "#lang rzk-1\n#set-option \"rstt-safe\" = \"off\"\n#set-option \"warn-shape-dependency\" = \"no\"\n#postulate mixed (A : U) (a : A) (s : 2) (f : (t : 2) → A [t === s ↦ a]) : Unit\n"
(errors, _, _) = run (Just RSTTSafeError) [source]
(warnErrors, warnings, _) = run (Just RSTTSafeWarn) [source]
errors `shouldBe` ["TypeErrorRSTT"]
warnErrors `shouldBe` []
warnings `shouldBe` ["RSTTShapeDependencyWarning"]
it "does not check shape dependencies by default" $ do
run Nothing
["#lang rzk-1\n#postulate mixed (A : U) (a : A) (s : 2) (f : (t : 2) → A [t === s ↦ a]) : Unit\n"]
`shouldBe` ([], [], 0)
it "warns about shape dependencies when the standalone option is on" $ do
run Nothing
["#lang rzk-1\n#set-option \"warn-shape-dependency\" = \"yes\"\n#postulate mixed (A : U) (a : A) (s : 2) (f : (t : 2) → A [t === s ↦ a]) : Unit\n"]
`shouldBe` ([], ["RSTTShapeDependencyWarning"], 0)
it "restores the standalone shape-dependency default on unset" $ do
run Nothing
["#lang rzk-1\n#set-option \"warn-shape-dependency\" = \"yes\"\n#unset-option \"warn-shape-dependency\"\n#postulate mixed (A : U) (a : A) (s : 2) (f : (t : 2) → A [t === s ↦ a]) : Unit\n"]
`shouldBe` ([], [], 0)
it "restores an explicit shape-dependency opt-out after safe mode" $ do
run Nothing
["#lang rzk-1\n#set-option \"warn-shape-dependency\" = \"no\"\n#set-option \"rstt-safe\" = \"error\"\n#set-option \"rstt-safe\" = \"off\"\n#postulate mixed (A : U) (a : A) (s : 2) (f : (t : 2) → A [t === s ↦ a]) : Unit\n"]
`shouldBe` ([], [], 0)
it "keeps shape-dependency checks enabled by source safe mode" $ do
let (errors, _, _) = run Nothing
["#lang rzk-1\n#set-option \"rstt-safe\" = \"error\"\n#set-option \"warn-shape-dependency\" = \"no\"\n#postulate mixed (A : U) (a : A) (s : 2) (f : (t : 2) → A [t === s ↦ a]) : Unit\n"]
errors `shouldBe` ["TypeErrorRSTT"]
it "keeps restriction checks enabled by source safe mode" $ do
let (errors, _, _) = run Nothing
["#lang rzk-1\n#set-option \"rstt-safe\" = \"error\"\n#set-option \"warn-free-standing-restriction\" = \"no\"\n#postulate restricted (A : U) (a : A) : A [TOP ↦ a]\n"]
errors `shouldBe` ["TypeErrorRSTT"]
it "restores standalone restriction settings when safe mode is turned off" $ do
run Nothing
["#lang rzk-1\n#set-option \"warn-free-standing-restriction\" = \"no\"\n#set-option \"rstt-safe\" = \"warn\"\n#set-option \"rstt-safe\" = \"off\"\n#postulate restricted (A : U) (a : A) : A [TOP ↦ a]\n"]
`shouldBe` ([], [], 0)
it "resets the standalone binder default even while safe mode is active" $ do
run Nothing
["#lang rzk-1\n#set-option \"warn-meta-binder\" = \"yes\"\n#unset-option \"warn-meta-binder\"\n#set-option \"rstt-safe\" = \"off\"\n" <> nested]
`shouldBe` ([], [], 0)
it "rejects a hole even when the API allows unfinished terms" $ do
let (errors, warnings, count) = run (Just RSTTSafeError)
["#lang rzk-1\n#def unfinished : Unit := ?\n"]
errors `shouldBe` ["TypeErrorRSTT"]
warnings `shouldBe` ["RSTTHoleWarning"]
count `shouldBe` 1
it "reports unfinished obligations without failing in warning mode" $ do
run (Just RSTTSafeWarn) ["#lang rzk-1\n#def unfinished : Unit := ?\n"]
`shouldBe` ([], ["RSTTHoleWarning"], 1)
it "rejects outer-point capture introduced by an instantiated type family" $ do
let source = T.unlines
[ "#lang rzk-1"
, "#def extension (φ : 2 → TOPE) (A : U) (a : A) : U :="
, " (t : 2) → A [φ t ↦ a]"
, "#def at-point (A : U) (a : A) (s : 2)"
, " (f : extension (\\ t → t === s) A a) : A := f s"
]
(errors, warnings, _) = run (Just RSTTSafeError) [source]
errors `shouldBe` ["TypeErrorRSTT"]
warnings `shouldBe` ["RSTTShapeDependencyWarning"]
it "exposes shapes behind lets and both projections" $ do
let shape = "(t : 2) → A [φ t ↦ a]"
bodies =
[ "let X : U := " <> shape <> " in X"
, "first ((" <> shape <> "), unit)"
, "second (unit, (" <> shape <> "))"
]
mapM_ (\body -> do
let source = T.unlines
[ "#lang rzk-1"
, "#def extension (φ : 2 → TOPE) (A : U) (a : A) : U := " <> body
, "#postulate bad (A : U) (a : A) (s : 2)"
, " (f : extension (\\ t → t === s) A a) : Unit"
]
(errors, warnings, _) = run (Just RSTTSafeError) [source]
errors `shouldBe` ["TypeErrorRSTT"]
warnings `shouldBe` ["RSTTShapeDependencyWarning"]) bodies
it "checks argument domains created by earlier schematic arguments" $ do
let source = T.unlines
[ "#lang rzk-1"
, "#postulate schema (φ : 2 → TOPE) (A : U) (a : A)"
, " (f : (t : 2) → A [φ t ↦ a]) : Unit"
, "#def bad (A : U) (a : A) (s : 2) : Unit :="
, " schema (\\ t → t === s) A a (\\ t → a)"
]
(errors, warnings, _) = run (Just RSTTSafeError) [source]
errors `shouldBe` ["TypeErrorRSTT"]
warnings `shouldBe` ["RSTTShapeDependencyWarning"]
it "allows independent shapes after unfolding and beta reduction" $ do
run (Just RSTTSafeError) [T.unlines
[ "#lang rzk-1"
, "#def extension (φ : 2 → TOPE) (A : U) (a : A) : U :="
, " (t : 2) → A [φ t ↦ a]"
, "#def at-point (A : U) (a : A) (s : 2)"
, " (f : extension (\\ t → t === 0_2) A a) : A := f s"
]] `shouldBe` ([], [], 0)
it "finds contextual domains after instantiation, let reduction and projection" $ do
let source = T.unlines
[ "#lang rzk-1"
, "#def shape (φ : 2 → TOPE) (A : U) : U := (t : 2 | φ t) → A"
, "#postulate bad (A : U) (s : 2)"
, " (f : first ((let X := shape (\\ t → t <= s) A in X), unit)) : Unit"
]
(errors, warnings, _) = run (Just RSTTSafeWarn) [source]
errors `shouldBe` []
warnings `shouldSatisfy` elem "RSTTShapeDependencyWarning"
it "preserves dependencies even when a guard simplifies to top" $ do
let source = T.unlines
[ "#lang rzk-1"
, "#def extension (φ : 2 → TOPE) (A : U) (a : A) : U :="
, " (t : 2) → A [φ t ↦ a]"
, "#postulate bad (A : U) (a : A) (s : 2)"
, " (f : extension (\\ t → s === s) A a) : Unit"
]
(errors, warnings, _) = run (Just RSTTSafeError) [source]
errors `shouldBe` ["TypeErrorRSTT"]
warnings `shouldBe` ["RSTTShapeDependencyWarning"]
it "preserves concluded tails after unfolding" $ do
let source = T.unlines
[ "#lang rzk-1"
, "#set-option \"rstt-safe\" = \"off\""
, "#def restricted (A : U) (a : A) : U := A [TOP ↦ a]"
, "#set-option \"rstt-safe\" = \"error\""
, "#def good (A : U) (a : A) : restricted A a := a"
]
run Nothing [source] `shouldBe` ([], [], 0)
it "reports an incomplete audit instead of silently accepting a reduction limit" $ do
let source = T.unlines $ ["#lang rzk-1", "#def T0 : U := Unit"] <>
[ "#def T" <> T.pack (show i) <> " : U := T" <> T.pack (show (i - 1))
| i <- [1 .. 260 :: Int] ]
(errors, warnings, _) = run (Just RSTTSafeWarn) [source]
(strictErrors, _, _) = run (Just RSTTSafeError) [source]
errors `shouldBe` []
warnings `shouldSatisfy` elem "RSTTIncompleteWarning"
strictErrors `shouldSatisfy` elem "TypeErrorRSTT"
it "audits computed argument types in standalone commands" $ do
let declarations = T.unlines
[ "#lang rzk-1"
, "#postulate schema (φ : 2 → TOPE) (A : U) (a : A)"
, " (f : (t : 2) → A [φ t ↦ a]) : Unit"
]
application = "(\\ (s : 2) → schema (\\ t → t === s) Unit unit (\\ t → unit))"
mapM_ (\command -> do
let (errors, warnings, _) = run (Just RSTTSafeError) [declarations <> command]
errors `shouldBe` ["TypeErrorRSTT"]
warnings `shouldBe` ["RSTTShapeDependencyWarning"])
[ "#check " <> application <> " : 2 → Unit"
, "#compute " <> application
, "#compute-nf " <> application
]
it "locates unsupported syntax below binders before it can reduce away" $ do
let source = "#lang rzk-1\n#def discard (i : 2) : Unit :=\n (\\ (j : 2) → unit) (sup i 1_2)\n"
(errors, warnings, _) = runDetails (Just RSTTSafeWarn) [source]
errors `shouldBe` []
map checkWarningTag warnings `shouldBe` ["RSTTSyntaxWarning"]
map warningLocation warnings `shouldBe`
[Just (LocationInfo (Just "1") (Just 3) (Just 23))]
it "checks standalone commands as well as declarations" $ do
let (errors, warnings, _) = run (Just RSTTSafeError)
["#lang rzk-1\n#check (\\ (j : 2) → unit) (sup 0_2 1_2) : Unit\n"]
errors `shouldBe` ["TypeErrorRSTT"]
warnings `shouldBe` ["RSTTSyntaxWarning"]
it "locates modal syntax inside an otherwise allowed term" $ do
let (errors, warnings, _) = runDetails (Just RSTTSafeWarn)
["#lang rzk-1\n#def modal : U :=\n (_# Unit)\n"]
errors `shouldBe` []
map checkWarningTag warnings `shouldBe` ["RSTTModalWarning"]
map warningLocation warnings `shouldBe`
[Just (LocationInfo (Just "1") (Just 3) (Just 4))]
it "locates match before elaboration turns it into an eliminator" $ do
let (errors, warnings, _) = runDetails (Just RSTTSafeWarn)
["#lang rzk-1\n#data bool := false | true\n#def choose (b : bool) : bool :=\n match b (false ⇒ true | true ⇒ false)\n"]
errors `shouldBe` []
map checkWarningTag warnings `shouldBe`
["RSTTInductiveWarning", "RSTTInductiveWarning"]
map warningLocation warnings `shouldBe`
[ Just (LocationInfo (Just "1") (Just 2) (Just 1))
, Just (LocationInfo (Just "1") (Just 4) (Just 3)) ]
it "still rejects direct constructor uses when safe mode is re-enabled" $ do
let (errors, warnings, _) = runDetails Nothing
["#lang rzk-1\n#set-option \"rstt-safe\" = \"off\"\n#data bool := false | true\n#set-option \"rstt-safe\" = \"warn\"\n#def yes : bool := true\n"]
errors `shouldBe` []
map checkWarningTag warnings `shouldBe` ["RSTTInductiveWarning"]
map warningLocation warnings `shouldBe`
[Just (LocationInfo (Just "1") (Just 5) (Just 20))]
it "allows local binders that shadow constructor names" $ do
run Nothing
["#lang rzk-1\n#set-option \"rstt-safe\" = \"off\"\n#data bool := false | true\n#set-option \"rstt-safe\" = \"warn\"\n#def shadow (false : Unit) : Unit := false\n"]
`shouldBe` ([], [], 0)