packages feed

futhark-0.27.1: src-testing/Language/Futhark/TypeChecker/TySolveTests.hs

module Language.Futhark.TypeChecker.TySolveTests (tests) where

import Data.Loc (Loc (NoLoc))
import Data.Map qualified as M
import Futhark.Util.Pretty (docString)
import Language.Futhark.Syntax
import Language.Futhark.SyntaxTests ()
import Language.Futhark.TypeChecker.Constraints
  ( CtTy (..),
    Level,
    Reason (..),
    TyParams,
    TyVarInfo (..),
    TyVars,
  )
import Language.Futhark.TypeChecker.Monad (TypeError (TypeError), prettyTypeError)
import Language.Futhark.TypeChecker.TySolve
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (Assertion, assertBool, assertFailure, testCase, (@?=))
import Text.Regex.TDFA ((=~))

testSolve ::
  [CtTy ()] ->
  TyParams ->
  TyVars () ->
  ([UnconTyVar], Solution) ->
  Assertion
testSolve constraints typarams tyvars expected =
  case solve constraints typarams tyvars of
    Right s -> s @?= expected
    Left e -> assertFailure $ docString $ prettyTypeError e

testSolveFail ::
  [CtTy ()] ->
  TyParams ->
  TyVars () ->
  String ->
  Assertion
testSolveFail constraints typarams tyvars expected =
  case solve constraints typarams tyvars of
    Left (TypeError _ _ actualMsg) ->
      let regexMatch :: Bool = docString actualMsg =~ expected
       in assertBool "Regex doesn't match" regexMatch
    Right _ -> assertFailure "Expected type error, but got a solution"

-- When writing type variables/names here (a_0, b_1), make *sure* that
-- the numbers are distinct. These are all that actually matter for
-- determining identity.

(~) :: TypeBase () NoUniqueness -> TypeBase () NoUniqueness -> CtTy ()
t1 ~ t2 = CtEq (Reason mempty) t1 t2

tvFree :: VName -> Level -> (VName, (Level, TyVarInfo ()))
tvFree v lvl = (v, (lvl, TyVarFree mempty Unlifted))

tvRecord :: VName -> Level -> M.Map Name (TypeBase () NoUniqueness) -> (VName, (Level, TyVarInfo ()))
tvRecord v lvl fields = (v, (lvl, TyVarRecord mempty fields))

typaram :: VName -> Level -> Liftedness -> (VName, (Level, Liftedness, Loc))
typaram v lvl liftedness = (v, (lvl, liftedness, noLoc))

tests :: TestTree
tests =
  testGroup
    "Unsized constraint solver"
    [ testCase "infer unlifted" $
        testSolve
          [ "t\8320_9896" ~ "if_t\8322_9898",
            "t\8321_9897" ~ "if_t\8322_9898",
            "t\8323_9899" ~ "if_t\8322_9898"
          ]
          mempty
          ( M.fromList
              [ ("t\8320_9896", (2, TyVarFree NoLoc Lifted)),
                ("t\8321_9897", (3, TyVarFree NoLoc Lifted)),
                ("if_t\8322_9898", (4, TyVarFree NoLoc SizeLifted)),
                ("t\8323_9899", (5, TyVarFree NoLoc Lifted))
              ]
          )
          ( [("if_t\8322_9898", SizeLifted)],
            M.fromList
              [ ("t\8320_9896", Right "if_t\8322_9898"),
                ("t\8321_9897", Right "if_t\8322_9898"),
                ("t\8323_9899", Right "if_t\8322_9898")
              ]
          ),
      testCase "empty" $
        testSolve [] mempty mempty ([], mempty),
      testCase "b_1 ~ a_0" $
        testSolve
          ["b_1" ~ "a_0"]
          mempty
          (M.fromList [tvFree "b_1" 0])
          ([], M.fromList [("b_1", Right "a_0")]),
      testCase "a_0 ~ b_1" $
        testSolve
          ["a_0" ~ "b_1"]
          mempty
          (M.fromList [tvFree "a_0" 0, tvFree "b_1" 0])
          ([("b_1", Unlifted)], M.fromList [("a_0", Right "b_1")]),
      testCase "multiple" $
        testSolve
          ["b_1" ~ "a_0", "d_3" ~ "c_2", "e_4" ~ "c_2", "c_2" ~ "a_0"]
          mempty
          (M.fromList [tvFree "a_0" 0, tvFree "b_1" 0, tvFree "c_2" 0, tvFree "d_3" 0, tvFree "e_4" 0])
          ([("a_0", Unlifted)], M.fromList [("b_1", Right "a_0"), ("c_2", Right "a_0"), ("d_3", Right "a_0"), ("e_4", Right "a_0")]),
      testCase "Two variables" $
        testSolve
          ["a_0" ~ "b_1", "c_2" ~ "d_3"]
          mempty
          (M.fromList [tvFree "a_0" 0, tvFree "c_2" 0])
          ([], M.fromList [("a_0", Right "b_1"), ("c_2", Right "d_3")]),
      testCase "i32 + (i32 + i32)" $
        testSolve
          [ "i32 -> i32 -> a_0" ~ "i32 -> i32 -> i32",
            "i32 -> a_0 -> b_1" ~ "i32 -> i32 -> i32"
          ]
          mempty
          (M.fromList [tvFree "a_0" 0, tvFree "b_1" 0])
          ([], M.fromList [("a_0", Right "i32"), ("b_1", Right "i32")]),
      testCase "((λx -> λy -> x * y) i32) i32" $
        testSolve
          [ "a_0 -> b_1 -> c_2" ~ "i32 -> i32 -> i32",
            "a_0 -> b_1 -> c_2" ~ "i32 -> d_3",
            "d_3" ~ "i32 -> e_4"
          ]
          mempty
          (M.fromList [tvFree "a_0" 0, tvFree "b_1" 0, tvFree "c_2" 0, tvFree "d_3" 0, tvFree "e_4" 0])
          ( [],
            M.fromList
              [ ("a_0", Right "i32"),
                ("b_1", Right "i32"),
                ("c_2", Right "i32"),
                ("d_3", Right "i32 -> i32"),
                ("e_4", Right "i32")
              ]
          ),
      testCase "rec λf -> λn -> if n == 0 then 1 else n * (f (n - 1))" $
        testSolve
          [ "b_1 -> i32 -> c_2" ~ "i32 -> i32 -> bool",
            "b_1 -> i32 -> d_3" ~ "i32 -> i32 -> i32",
            "a_0" ~ "d_3 -> e_4",
            "b_1 -> e_4 -> f_5" ~ "i32 -> i32 -> i32",
            "c_2" ~ "bool",
            "i32" ~ "f_5",
            "g_6 -> g_6" ~ "a_0 -> b_1 -> i32"
          ]
          mempty
          (M.fromList [tvFree "a_0" 0, tvFree "b_1" 0, tvFree "c_2" 0, tvFree "d_3" 0, tvFree "e_4" 0, tvFree "f_5" 0, tvFree "g_6" 0])
          ( [],
            M.fromList
              [ ("a_0", Right "i32 -> i32"),
                ("b_1", Right "i32"),
                ("c_2", Right "bool"),
                ("d_3", Right "i32"),
                ("e_4", Right "i32"),
                ("f_5", Right "i32"),
                ("g_6", Right "i32 -> i32")
              ]
          ),
      testCase "let id = λx -> x in id id" $
        testSolve
          ["b_1 -> b_1" ~ "(c_2 -> c_2) -> d_3"]
          mempty
          (M.fromList [tvFree "b_1" 0, tvFree "c_2" 0, tvFree "d_3" 0])
          ( [("c_2", Unlifted)],
            M.fromList
              [ ("b_1", Right "c_2 -> c_2"),
                ("d_3", Right "c_2 -> c_2")
              ]
          ),
      testCase "a_0 ~ i32" $
        testSolve
          ["a_0" ~ "i32"]
          mempty
          (M.fromList [tvFree "a_0" 0])
          ([], M.fromList [("a_0", Right "i32")]),
      testCase "a_0 ~ a_0" $
        testSolve
          ["a_0" ~ "a_0"]
          mempty
          (M.fromList [tvFree "a_0" 0])
          ([("a_0", Unlifted)], mempty),
      testCase "non-unifiable types" $
        testSolveFail
          ["a_0" ~ "i32", "a_0" ~ "bool"]
          mempty
          (M.fromList [tvFree "a_0" 0])
          ".?([Cc]annot unify).?",
      testCase "infinite type (function) 1" $
        testSolveFail
          ["a_0" ~ "a_0 -> b_1"]
          mempty
          (M.fromList [tvFree "a_0" 0])
          ".?([Oo]ccurs check).?",
      -- ! This case acts weird for the original implementation.
      testCase "infinite type (function) 2" $
        testSolveFail
          ["a_0" ~ "b_1 -> i32", "b_1" ~ "c_2", "b_1" ~ "d_3", "a_0" ~ "d_3"]
          mempty
          (M.fromList [tvFree "a_0" 0, tvFree "b_1" 0, tvFree "c_2" 0, tvFree "d_3" 0])
          ".?([Oo]ccurs check).?",
      testCase "infinite type (list)" $
        testSolveFail
          ["a_0" ~ "[]a_0"]
          mempty
          (M.fromList [tvFree "a_0" 0])
          ".?([Oo]ccurs check).?",
      testCase "infinite type (tuple)" $
        testSolveFail
          ["a_0" ~ "(a_0, bool)"]
          mempty
          (M.fromList [tvFree "a_0" 0])
          ".?([Oo]ccurs check).?",
      testCase "infinite type (record) 1" $
        testSolveFail
          ["a_0" ~ "{foo: a_0, bar: f32}"]
          mempty
          (M.fromList [tvFree "a_0" 0])
          ".?([Oo]ccurs check).?",
      testCase "infinite type (record) 2" $
        testSolveFail
          ["a_0" ~ "{foo: b_1}", "b_1" ~ "c_2", "a_0" ~ "c_2"]
          mempty
          (M.fromList [tvFree "a_0" 0, tvFree "b_1" 0, tvFree "c_2" 0])
          ".?([Oo]ccurs check).?",
      testCase "infinite type (record) 3" $
        testSolveFail
          ["a_0" ~ "{foo: b_1}", "c_2" ~ "b_1", "a_0" ~ "c_2"]
          mempty
          (M.fromList [tvFree "a_0" 0, tvFree "b_1" 0, tvFree "c_2" 0])
          ".?([Oo]ccurs check).?",
      testCase "infinite type (record) 4" $
        testSolveFail
          ["a_0" ~ "{foo: b_1}", "c_2" ~ "b_1", "d_3" ~ "c_2", "a_0" ~ "c_2"]
          mempty
          (M.fromList [tvFree "a_0" 0, tvFree "b_1" 0, tvFree "c_2" 0, tvFree "d_3" 0])
          ".?([Oo]ccurs check).?",
      testCase "infinite type (consuming array param)" $
        testSolveFail
          ["a_0" ~ "*[]a_0"]
          mempty
          (M.fromList [tvFree "a_0" 0])
          ".?([Oo]ccurs check).?",
      -- ! This case acts weird for the original implementation.
      testCase "infinite type (nested)" $
        testSolveFail
          ["a_0" ~ "{foo: i32, bar: b_1}", "b_1" ~ "c_2", "c_2" ~ "i32 -> []a_0"]
          mempty
          (M.fromList [tvFree "a_0" 0, tvFree "b_1" 0, tvFree "c_2" 0])
          ".?([Oo]ccurs check).?",
      testCase "vector and 2D matrix" $
        testSolveFail
          ["a_0" ~ "[]i32", "a_0" ~ "[][]i32"]
          mempty
          (M.fromList [tvFree "a_0" 0])
          ".?([Cc]annot unify).?",
      testCase "different array types" $
        testSolveFail
          ["a_0" ~ "[]f64", "a_0" ~ "[]i64"]
          mempty
          (M.fromList [tvFree "a_0" 0])
          ".?([Cc]annot unify).?",
      testCase "simple record" $
        testSolve
          ["a_0" ~ "{foo: i32, bar: bool}"]
          mempty
          (M.fromList [tvFree "a_0" 0])
          ([], M.fromList [("a_0", Right "{foo: i32, bar: bool}")]),
      testCase "record 2" $
        testSolve
          ["a_0" ~ "{foo: b_1, bar: c_2}", "b_1" ~ "c_2", "c_2" ~ "i64"]
          mempty
          (M.fromList [tvFree "a_0" 0, tvFree "b_1" 0, tvFree "c_2" 0])
          ( [],
            M.fromList
              [ ("a_0", Right "{foo: i64, bar: i64}"),
                ("b_1", Right "i64"),
                ("c_2", Right "i64")
              ]
          ),
      testCase "record 3" $
        testSolve
          ["a_0" ~ "{foo: b_1, bar: c_2}", "b_1" ~ "c_2"]
          (M.fromList [typaram "c_2" 0 Lifted])
          (M.fromList [tvFree "a_0" 0, tvFree "b_1" 0])
          ( [],
            M.fromList
              [ ("a_0", Right "{foo: c_2, bar: c_2}"),
                ("b_1", Right "c_2")
              ]
          ),
      testCase "tuple" $
        testSolve
          ["a_0" ~ "(b_1, c_2, d_3)", "c_2" ~ "d_3"]
          mempty
          (M.fromList [tvFree "a_0" 0, tvFree "b_1" 0, tvFree "c_2" 0, tvFree "d_3" 0])
          ( [("b_1", Unlifted), ("d_3", Unlifted)],
            M.fromList
              [ ("a_0", Right "(b_1, d_3, d_3)"),
                ("c_2", Right "d_3")
              ]
          ),
      testCase "compatible levels" $
        testSolve
          ["a_0" ~ "b_1"]
          (M.fromList [typaram "a_0" 0 Unlifted])
          (M.fromList [tvFree "b_1" 1])
          ([], M.fromList [("b_1", Right "a_0")]),
      testCase "scope violation 1" $
        testSolveFail
          ["a_0" ~ "b_1"]
          (M.fromList [typaram "b_1" 1 Unlifted])
          (M.fromList [tvFree "a_0" 0])
          ".?(scope violation).?",
      testCase "scope violation 2" $
        testSolveFail
          ["a_0" ~ "b_1", "b_1" ~ "c_2"]
          (M.fromList [typaram "c_2" 1 Unlifted])
          (M.fromList [tvFree "a_0" 0, tvFree "b_1" 1])
          ".?(scope violation).?",
      testCase "differently sized tuples" $
        testSolveFail
          ["a_0" ~ "(i32, c_2)", "b_1" ~ "(i32, c_2, bool)", "a_0" ~ "b_1"]
          mempty
          (M.fromList [tvFree "a_0" 0, tvFree "b_1" 0])
          ".?([Cc]annot unify).?",
      testCase "Prim type last substitution" $
        testSolve
          [ "t\8321_8321" ~ "num\8320_8320",
            "index\8322_8322" ~ "index_elem\8323_8323",
            "[]t_0" ~ "[]index_elem\8323_8323"
          ]
          (M.fromList [typaram "t_0" 0 Unlifted])
          ( M.fromList
              [ ("num\8320_8320", (2, TyVarPrim NoLoc [Signed Int8, Signed Int16, Signed Int32, Signed Int64, Unsigned Int8, Unsigned Int16, Unsigned Int32, Unsigned Int64, FloatType Float16, FloatType Float32, FloatType Float64])),
                ("t\8321_8321", (2, TyVarPrim NoLoc [Signed Int8, Signed Int16, Signed Int32, Signed Int64])),
                ("index\8322_8322", (2, TyVarFree NoLoc Unlifted)),
                ("index_elem\8323_8323", (2, TyVarFree NoLoc Unlifted))
              ]
          )
          ( [],
            M.fromList
              [ ("num\8320_8320", Left [Signed Int8, Signed Int16, Signed Int32, Signed Int64]),
                ("t\8321_8321", Left [Signed Int8, Signed Int16, Signed Int32, Signed Int64]),
                ("index\8322_8322", Right "t_0"),
                ("index_elem\8323_8323", Right "t_0")
              ]
          ),
      testCase "record with polymorphic fields" $
        testSolve
          [ "d_3" ~ "{foo: e_4, bar: f_5}",
            "e_4" ~ "i32",
            "f64" ~ "f_5",
            "a_0" ~ "d_3"
          ]
          mempty
          ( M.fromList
              [ tvRecord "a_0" 0 $
                  M.fromList
                    [ ("foo", Scalar (Prim (Signed Int32))),
                      ("bar", Scalar (Prim (FloatType Float64)))
                    ],
                tvFree "d_3" 0,
                tvFree "e_4" 0,
                tvFree "f_5" 0
              ]
          )
          ( [],
            M.fromList
              [ ("a_0", Right "{foo: i32, bar: f64}"),
                ("d_3", Right "{foo: i32, bar: f64}"),
                ("e_4", Right "i32"),
                ("f_5", Right "f64")
              ]
          ),
      testCase "opaque type" $
        testSolveFail
          ["a_0" ~ "i32"]
          mempty
          mempty
          ".?([Cc]annot unify).?",
      testCase "liftedness propagation (Lifted -> SizeLifted)" $
        testSolve
          ["a_0" ~ "b_1"]
          mempty
          (M.fromList [("a_0", (0, TyVarFree mempty SizeLifted)), ("b_1", (0, TyVarFree mempty Lifted))])
          ([("b_1", SizeLifted)], M.fromList [("a_0", Right "b_1")]),
      testCase "liftedness propagation (Lifted -> Unlifted)" $
        testSolve
          ["a_0" ~ "b_1"]
          mempty
          (M.fromList [("a_0", (0, TyVarFree mempty Unlifted)), ("b_1", (0, TyVarFree mempty Lifted))])
          ([("b_1", Unlifted)], M.fromList [("a_0", Right "b_1")]),
      testCase "liftedness propagation (SizeLifted -> Unlifted)" $
        testSolve
          ["a_0" ~ "b_1"]
          mempty
          (M.fromList [("a_0", (0, TyVarFree mempty Unlifted)), ("b_1", (0, TyVarFree mempty SizeLifted))])
          ([("b_1", Unlifted)], M.fromList [("a_0", Right "b_1")])
    ]