packages feed

rzk-0.9.2: test/Rzk/BinderTypesSpec.hs

{-# LANGUAGE OverloadedStrings #-}
-- | Tests for 'declBinderTypes', the elaborated types of local binders that
-- the LSP hover consumes. The interesting cases are the ones the surface
-- annotations cannot provide: bare lambda binders (typed by the domain of
-- the lambda's own Π-type) and pair patterns, whose dependent components are
-- instantiated at the earlier components.
module Rzk.BinderTypesSpec (spec) where

import qualified Data.Text                as T

import           Language.Rzk.Free.Syntax (RzkPosition (RzkPosition),
                                           getVarIdent)
import qualified Language.Rzk.Syntax      as Rzk
import           Language.Rzk.Syntax      (VarIdent' (VarIdent))
import           Rzk.TypeCheck

import           Test.Hspec

-- | Parse and typecheck a module, returning rendered binder types keyed by
-- the binder's (1-based) source position. Errors out loudly so a broken
-- fixture is obvious.
binderTypesOf :: T.Text -> [((Int, Int), String)]
binderTypesOf src =
  case Rzk.parseModule src of
    Left err -> error ("parse error: " <> T.unpack err)
    Right m  -> case defaultTypeCheck (localVerbosity Silent (typecheckModulesWithLocation [("<test>", m)])) of
      Left err    -> error ("typecheck threw: " <> ppTypeErrorInScopedContext' BottomUp err)
      Right decls ->
        let ds = concatMap snd decls
        in [ (pos, view t)
           | (v, t) <- binderTypesInScopeOf ds ds
           , let VarIdent (RzkPosition _ mpos) _ = getVarIdent v
           , Just pos <- [mpos]
           ]
  where
    view (TypeView t)       = show t
    view (ShapeView c tope) = show c <> " | " <> show tope

exampleModule :: T.Text
exampleModule = T.unlines
  [ "#lang rzk-1"                                        -- line 1
  , "#define comp (A : U) (f : A -> A) : A -> A"         -- line 2
  , "  := \\ z -> let w := f z in f w"                   -- line 3
  , "#define dep"                                        -- line 4
  , "  ( A : U)"                                         -- line 5
  , "  ( B : A -> U)"                                    -- line 6
  , "  ( C : (x : A) -> B x -> U)"                       -- line 7
  , "  ( (p , q , r) : Sigma (x : A , y : B x) , C x y)" -- line 8
  , "  : U"                                              -- line 9
  , "  := A"                                             -- line 10
  , "#define uncur (A : U) (B : A -> U)"                 -- line 11
  , "  : (Sigma (x : A) , B x) -> U"                     -- line 12
  , "  := \\ (a , b) -> A"                               -- line 13
  , "#define quasi-pair (A : U) (B : A -> U) : U"        -- line 14
  , "  := Sigma (x : A) , B x"                           -- line 15
  , "#define use-quasi (A : U) (B : A -> U)"             -- line 16
  , "  ( (s , t) : quasi-pair A B)"                      -- line 17
  , "  : U := A"                                         -- line 18
  , "#define shaped (A : U) (a : A)"                     -- line 19
  , "  ( p : (t : 2 | t === t) -> A)"                    -- line 20
  , "  : (s : 2 | TOP) -> A"                             -- line 21
  , "  := \\ r -> a"                                     -- line 22
  ]

spec :: Spec
spec = describe "declBinderTypes" $ do
  let entries = binderTypesOf exampleModule
      at pos = lookup pos entries

  it "types an annotated parameter" $
    at (2, 15) `shouldBe` Just "U"                    -- (A : U)

  it "types a bare lambda binder from the enclosing Π-type" $
    at (3, 8) `shouldBe` Just "A"                     -- \ z ->

  it "types an unannotated let binder from its value" $
    at (3, 17) `shouldBe` Just "A"                    -- let w := f z

  it "types a Π binder inside a parameter's type" $
    at (7, 10) `shouldBe` Just "A"                    -- C : (x : A) -> ...

  it "instantiates dependent tuple components at earlier components" $ do
    at (8, 6)  `shouldBe` Just "A"                    -- p
    at (8, 10) `shouldBe` Just "B p"                  -- q
    at (8, 14) `shouldBe` Just "C p q"                -- r

  it "types a bare pair lambda against its Σ domain" $ do
    at (13, 9)  `shouldBe` Just "A"                   -- \ (a , b) ->
    at (13, 13) `shouldBe` Just "B a"

  it "splits a pair against a defined Σ-type by computing its WHNF" $ do
    at (17, 6)  `shouldBe` Just "A"                   -- ( (s , t) : quasi-pair A B)
    at (17, 10) `shouldBe` Just "B s"

  it "shows a shaped binder with its tope" $
    at (20, 10) `shouldBe` Just "2 | t ≡ t"           -- (t : 2 | t === t)

  it "shows a bare lambda binder against a shaped Π with its tope" $
    at (22, 8) `shouldBe` Just "2 | ⊤"                -- \ r -> against (s : 2 | TOP) -> A