hnix/src/Nix/Type/Infer.hs

678 lines
23 KiB
Haskell
Raw Normal View History

{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TupleSections #-}
2018-11-17 22:21:03 +01:00
{-# LANGUAGE TypeApplications #-}
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# OPTIONS_GHC -Wno-name-shadowing #-}
module Nix.Type.Infer (
Constraint(..),
TypeError(..),
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
InferError(..),
Subst(..),
inferTop
) where
import Control.Applicative
import Control.Arrow
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
import Control.Monad.Catch
import Control.Monad.Except
2018-10-27 13:37:02 +02:00
import Control.Monad.Fail
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
import Control.Monad.Logic
import Control.Monad.Reader
import Control.Monad.Ref
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
import Control.Monad.ST
2019-03-09 23:52:20 +01:00
import Control.Monad.State.Strict
import Data.Fix
import Data.Foldable
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
import qualified Data.HashMap.Lazy as M
import Data.List (delete, find, nub, intersect, (\\))
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Maybe (fromJust)
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
import Data.STRef
import qualified Data.Set as Set
import Data.Text (Text)
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
import Nix.Atoms
import Nix.Convert
import Nix.Eval (MonadEval(..))
import qualified Nix.Eval as Eval
import Nix.Expr.Types
import Nix.Expr.Types.Annotated
import Nix.Fresh
import Nix.String
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
import Nix.Scope
import Nix.Thunk
import Nix.Thunk.Basic
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
import qualified Nix.Type.Assumption as As
import Nix.Type.Env
import qualified Nix.Type.Env as Env
import Nix.Type.Type
import Nix.Utils
-------------------------------------------------------------------------------
-- Classes
-------------------------------------------------------------------------------
-- | Inference monad
2019-03-09 19:24:14 +01:00
newtype InferT s m a = InferT
{ getInfer ::
2019-03-09 19:24:14 +01:00
ReaderT (Set.Set TVar, Scopes (InferT s m) (JThunkT s m))
(StateT InferState (ExceptT InferError m)) a
}
deriving (Functor, Applicative, Alternative, Monad, MonadPlus, MonadFix,
2019-03-09 19:24:14 +01:00
MonadReader (Set.Set TVar, Scopes (InferT s m) (JThunkT s m)), MonadFail,
MonadState InferState, MonadError InferError,
MonadFreshId i)
-- | Inference state
newtype InferState = InferState { count :: Int }
-- | Initial inference state
initInfer :: InferState
initInfer = InferState { count = 0 }
data Constraint
= EqConst Type Type
| ExpInstConst Type Scheme
| ImpInstConst Type (Set.Set TVar) Type
deriving (Show, Eq, Ord)
newtype Subst = Subst (Map TVar Type)
deriving (Eq, Ord, Show, Semigroup, Monoid)
class Substitutable a where
apply :: Subst -> a -> a
instance Substitutable TVar where
apply (Subst s) a = tv
where t = TVar a
(TVar tv) = Map.findWithDefault t a s
instance Substitutable Type where
apply _ (TCon a) = TCon a
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
apply s (TSet b a) = TSet b (M.map (apply s) a)
apply s (TList a) = TList (map (apply s) a)
apply (Subst s) t@(TVar a) = Map.findWithDefault t a s
2018-05-18 07:16:50 +02:00
apply s (t1 :~> t2) = apply s t1 :~> apply s t2
apply s (TMany ts) = TMany (map (apply s) ts)
instance Substitutable Scheme where
apply (Subst s) (Forall as t) = Forall as $ apply s' t
where s' = Subst $ foldr Map.delete s as
instance Substitutable Constraint where
apply s (EqConst t1 t2) = EqConst (apply s t1) (apply s t2)
apply s (ExpInstConst t sc) = ExpInstConst (apply s t) (apply s sc)
apply s (ImpInstConst t1 ms t2) = ImpInstConst (apply s t1) (apply s ms) (apply s t2)
instance Substitutable a => Substitutable [a] where
apply = map . apply
instance (Ord a, Substitutable a) => Substitutable (Set.Set a) where
apply = Set.map . apply
class FreeTypeVars a where
ftv :: a -> Set.Set TVar
instance FreeTypeVars Type where
2018-05-18 07:16:50 +02:00
ftv TCon{} = Set.empty
ftv (TVar a) = Set.singleton a
ftv (TSet _ a) = Set.unions (map ftv (M.elems a))
ftv (TList a) = Set.unions (map ftv a)
ftv (t1 :~> t2) = ftv t1 `Set.union` ftv t2
ftv (TMany ts) = Set.unions (map ftv ts)
instance FreeTypeVars TVar where
ftv = Set.singleton
instance FreeTypeVars Scheme where
ftv (Forall as t) = ftv t `Set.difference` Set.fromList as
instance FreeTypeVars a => FreeTypeVars [a] where
ftv = foldr (Set.union . ftv) Set.empty
instance (Ord a, FreeTypeVars a) => FreeTypeVars (Set.Set a) where
ftv = foldr (Set.union . ftv) Set.empty
class ActiveTypeVars a where
atv :: a -> Set.Set TVar
instance ActiveTypeVars Constraint where
atv (EqConst t1 t2) = ftv t1 `Set.union` ftv t2
atv (ImpInstConst t1 ms t2) = ftv t1 `Set.union` (ftv ms `Set.intersection` ftv t2)
atv (ExpInstConst t s) = ftv t `Set.union` ftv s
instance ActiveTypeVars a => ActiveTypeVars [a] where
atv = foldr (Set.union . atv) Set.empty
data TypeError
= UnificationFail Type Type
| InfiniteType TVar Type
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
| UnboundVariables [Text]
| Ambigious [Constraint]
| UnificationMismatch [Type] [Type]
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
deriving (Eq, Show)
data InferError
= TypeInferenceErrors [TypeError]
| TypeInferenceAborted
| forall s. Exception s => EvaluationError s
typeError :: MonadError InferError m => TypeError -> m ()
typeError err = throwError $ TypeInferenceErrors [err]
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
deriving instance Show InferError
instance Exception InferError
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
instance Semigroup InferError where
x <> _ = x
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
instance Monoid InferError where
mempty = TypeInferenceAborted
mappend = (<>)
-------------------------------------------------------------------------------
-- Inference
-------------------------------------------------------------------------------
-- | Run the inference monad
2019-03-09 19:24:14 +01:00
runInfer' ::
( MonadFreshId Int m
, MonadAtomicRef m
, Ref m ~ STRef s
, MonadFix m
) => InferT s m a -> m (Either InferError a)
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
runInfer' = runExceptT
. (`evalStateT` initInfer)
. (`runReaderT` (Set.empty, emptyScopes))
. getInfer
2019-03-09 21:15:19 +01:00
runInfer :: (forall s. InferT s (FreshIdT Int (ST s)) a) -> Either InferError a
runInfer m = runST (runFreshIdT 0 (runInfer' m))
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
2019-03-09 19:24:14 +01:00
inferType ::
( MonadFreshId Int m
, MonadAtomicRef m
, Ref m ~ STRef s
, MonadFix m
) => Env -> NExpr -> InferT s m [(Subst, Type)]
inferType env ex = do
Judgment as cs t <- infer ex
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
let unbounds = Set.fromList (As.keys as) `Set.difference`
Set.fromList (Env.keys env)
unless (Set.null unbounds) $
typeError $ UnboundVariables (nub (Set.toList unbounds))
let cs' = [ ExpInstConst t s
| (x, ss) <- Env.toList env
, s <- ss
, t <- As.lookup x as]
inferState <- get
let eres = (`evalState` inferState) $ runSolver $ do
subst <- solve (cs ++ cs')
return (subst, subst `apply` t)
case eres of
Left errs -> throwError $ TypeInferenceErrors errs
Right xs -> pure xs
-- | Solve for the toplevel type of an expression in a given environment
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
inferExpr :: Env -> NExpr -> Either InferError [Scheme]
inferExpr env ex = case runInfer (inferType env ex) of
Left err -> Left err
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
Right xs -> Right $ map (\(subst, ty) -> closeOver (subst `apply` ty)) xs
-- | Canonicalize and return the polymorphic toplevel type.
closeOver :: Type -> Scheme
closeOver = normalize . generalize Set.empty
2019-03-09 19:24:14 +01:00
extendMSet :: Monad m => TVar -> InferT s m a -> InferT s m a
extendMSet x = InferT . local (first (Set.insert x)) . getInfer
letters :: [String]
letters = [1..] >>= flip replicateM ['a'..'z']
2019-03-10 03:40:59 +01:00
freshTVar :: MonadState InferState m => m TVar
freshTVar = do
s <- get
put s{count = count s + 1}
2019-03-10 03:40:59 +01:00
return $ TV (letters !! count s)
fresh :: MonadState InferState m => m Type
fresh = TVar <$> freshTVar
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
instantiate :: MonadState InferState m => Scheme -> m Type
instantiate (Forall as t) = do
as' <- mapM (const fresh) as
let s = Subst $ Map.fromList $ zip as as'
return $ apply s t
generalize :: Set.Set TVar -> Type -> Scheme
generalize free t = Forall as t
where as = Set.toList $ ftv t `Set.difference` free
unops :: Type -> NUnaryOp -> [Constraint]
unops u1 = \case
NNot -> [ EqConst u1 (typeFun [typeBool, typeBool]) ]
NNeg -> [ EqConst u1 (TMany [ typeFun [typeInt, typeInt]
, typeFun [typeFloat, typeFloat] ]) ]
binops :: Type -> NBinaryOp -> [Constraint]
binops u1 = \case
NApp -> [] -- this is handled separately
-- Equality tells you nothing about the types, because any two types are
-- allowed.
NEq -> []
NNEq -> []
NGt -> inequality
NGte -> inequality
NLt -> inequality
NLte -> inequality
NAnd -> [ EqConst u1 (typeFun [typeBool, typeBool, typeBool]) ]
NOr -> [ EqConst u1 (typeFun [typeBool, typeBool, typeBool]) ]
NImpl -> [ EqConst u1 (typeFun [typeBool, typeBool, typeBool]) ]
NConcat -> [ EqConst u1 (TMany [ typeFun [typeList, typeList, typeList]
, typeFun [typeList, typeNull, typeList]
, typeFun [typeNull, typeList, typeList]
]) ]
NUpdate -> [ EqConst u1 (TMany [ typeFun [typeSet, typeSet, typeSet]
, typeFun [typeSet, typeNull, typeSet]
, typeFun [typeNull, typeSet, typeSet]
]) ]
NPlus -> [ EqConst u1 (TMany [ typeFun [typeInt, typeInt, typeInt]
, typeFun [typeFloat, typeFloat, typeFloat]
, typeFun [typeInt, typeFloat, typeFloat]
, typeFun [typeFloat, typeInt, typeFloat]
, typeFun [typeString, typeString, typeString]
, typeFun [typePath, typePath, typePath]
, typeFun [typeString, typeString, typePath]
]) ]
NMinus -> arithmetic
NMult -> arithmetic
NDiv -> arithmetic
where
inequality =
[ EqConst u1 (TMany [ typeFun [typeInt, typeInt, typeBool]
, typeFun [typeFloat, typeFloat, typeBool]
, typeFun [typeInt, typeFloat, typeBool]
, typeFun [typeFloat, typeInt, typeBool]
]) ]
arithmetic =
[ EqConst u1 (TMany [ typeFun [typeInt, typeInt, typeInt]
, typeFun [typeFloat, typeFloat, typeFloat]
, typeFun [typeInt, typeFloat, typeFloat]
, typeFun [typeFloat, typeInt, typeFloat]
]) ]
2019-03-09 19:24:14 +01:00
liftInfer :: Monad m => m a -> InferT s m a
liftInfer = InferT . lift . lift . lift
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
2019-03-09 19:24:14 +01:00
instance (MonadRef m, Ref m ~ STRef s) => MonadRef (InferT s m) where
type Ref (InferT s m) = Ref m
newRef x = liftInfer $ newRef x
readRef x = liftInfer $ readRef x
writeRef x y = liftInfer $ writeRef x y
2019-03-09 19:24:14 +01:00
instance (MonadAtomicRef m, Ref m ~ STRef s) => MonadAtomicRef (InferT s m) where
atomicModifyRef x f = liftInfer $ do
2019-03-09 19:24:14 +01:00
res <- snd . f <$> readRef x
_ <- modifyRef x (fst . f)
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
return res
newtype JThunkT s m = JThunk (NThunkF (InferT s m) (Judgment s))
2019-03-09 19:24:14 +01:00
instance Monad m => MonadThrow (InferT s m) where
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
throwM = throwError . EvaluationError
2019-03-09 19:24:14 +01:00
instance Monad m => MonadCatch (InferT s m) where
2018-05-03 00:07:30 +02:00
catch m h = catchError m $ \case
EvaluationError e ->
maybe (error $ "Exception was not an exception: " ++ show e) h
(fromException (toException e))
err -> error $ "Unexpected error: " ++ show err
2019-03-09 19:24:14 +01:00
instance ( MonadFreshId Int m
, MonadAtomicRef m
, Ref m ~ STRef s
) => MonadThunk (Judgment s) (JThunkT s m) (InferT s m) where
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
thunk = fmap JThunk . buildThunk
2018-05-03 00:07:30 +02:00
force (JThunk t) f = catch (forceThunk t f) $ \(_ :: ThunkLoop) ->
-- If we have a thunk loop, we just don't know the type.
f =<< Judgment As.empty [] <$> fresh
forceEff (JThunk t) f = catch (forceEffects t f) $ \(_ :: ThunkLoop) ->
-- If we have a thunk loop, we just don't know the type.
f =<< Judgment As.empty [] <$> fresh
2018-05-03 00:07:30 +02:00
wrapValue = JThunk . valueRef
getValue (JThunk x) = thunkValue x
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
2019-03-09 19:24:14 +01:00
instance ( MonadFreshId Int m
, MonadAtomicRef m
, Ref m ~ STRef s
, MonadFix m
) => MonadEval (Judgment s) (InferT s m) where
freeVariable var = do
tv <- fresh
return $ Judgment (As.singleton var tv) [] tv
2018-11-18 00:20:59 +01:00
synHole var = do
tv <- fresh
return $ Judgment (As.singleton var tv) [] tv
2018-05-03 00:07:30 +02:00
-- If we fail to look up an attribute, we just don't know the type.
attrMissing _ _ = Judgment As.empty [] <$> fresh
evaledSym _ = pure
evalCurPos =
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
return $ Judgment As.empty [] $ TSet False $ M.fromList
[ ("file", typePath)
, ("line", typeInt)
, ("col", typeInt) ]
evalConstant c = return $ Judgment As.empty [] (go c)
where
go = \case
NInt _ -> typeInt
NFloat _ -> typeFloat
NBool _ -> typeBool
NNull -> typeNull
evalString = const $ return $ Judgment As.empty [] typeString
evalLiteralPath = const $ return $ Judgment As.empty [] typePath
evalEnvPath = const $ return $ Judgment As.empty [] typePath
evalUnary op (Judgment as1 cs1 t1) = do
tv <- fresh
2018-05-18 07:16:50 +02:00
return $ Judgment as1 (cs1 ++ unops (t1 :~> tv) op) tv
evalBinary op (Judgment as1 cs1 t1) e2 = do
Judgment as2 cs2 t2 <- e2
tv <- fresh
return $ Judgment
(as1 `As.merge` as2)
2018-05-18 07:16:50 +02:00
(cs1 ++ cs2 ++ binops (t1 :~> t2 :~> tv) op)
tv
2018-05-03 00:07:30 +02:00
evalWith = Eval.evalWithAttrSet
evalIf (Judgment as1 cs1 t1) t f = do
Judgment as2 cs2 t2 <- t
Judgment as3 cs3 t3 <- f
return $ Judgment
(as1 `As.merge` as2 `As.merge` as3)
(cs1 ++ cs2 ++ cs3 ++ [EqConst t1 typeBool, EqConst t2 t3])
t2
evalAssert (Judgment as1 cs1 t1) body = do
Judgment as2 cs2 t2 <- body
return $ Judgment
(as1 `As.merge` as2)
(cs1 ++ cs2 ++ [EqConst t1 typeBool])
t2
evalApp (Judgment as1 cs1 t1) e2 = do
Judgment as2 cs2 t2 <- e2
tv <- fresh
return $ Judgment
(as1 `As.merge` as2)
2018-05-18 07:16:50 +02:00
(cs1 ++ cs2 ++ [EqConst t1 (t2 :~> tv)])
tv
evalAbs (Param x) k = do
2019-03-10 03:40:59 +01:00
a <- freshTVar
let tv = TVar a
((), Judgment as cs t) <-
extendMSet a (k (pure (Judgment (As.singleton x tv) [] tv))
(\_ b -> ((),) <$> b))
return $ Judgment
(as `As.remove` x)
(cs ++ [EqConst t' tv | t' <- As.lookup x as])
2018-05-18 07:16:50 +02:00
(tv :~> t)
evalAbs (ParamSet ps variadic _mname) k = do
js <- fmap concat $ forM ps $ \(name, _) -> do
tv <- fresh
pure [(name, tv)]
let (env, tys) = (\f -> foldl' f (As.empty, M.empty) js)
$ \(as1, t1) (k, t) ->
(as1 `As.merge` As.singleton k t, M.insert k t t1)
arg = pure $ Judgment env [] (TSet True tys)
call = k arg $ \args b -> (args,) <$> b
names = map fst js
(args, Judgment as cs t) <-
foldr (\(_, TVar a) -> extendMSet a) call js
ty <- TSet variadic <$> traverse (inferredType <$>) args
return $ Judgment
(foldl' As.remove as names)
(cs ++ [ EqConst t' (tys M.! x)
| x <- names
, t' <- As.lookup x as])
2018-05-18 07:16:50 +02:00
(ty :~> t)
evalError = throwError . EvaluationError
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
data Judgment s = Judgment
{ assumptions :: As.Assumption
, typeConstraints :: [Constraint]
, inferredType :: Type
}
deriving Show
2019-03-09 19:24:14 +01:00
instance Monad m => FromValue NixString (InferT s m) (Judgment s) where
fromValueMay _ = return Nothing
fromValue _ = error "Unused"
2019-03-09 19:24:14 +01:00
instance ( MonadFreshId Int m
, MonadAtomicRef m
, Ref m ~ STRef s
, MonadFix m
) => FromValue (AttrSet (JThunkT s m), AttrSet SourcePos) (InferT s m) (Judgment s) where
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
fromValueMay (Judgment _ _ (TSet _ xs)) = do
let sing _ = Judgment As.empty []
pure $ Just (M.mapWithKey (\k v -> wrapValue (sing k v)) xs, M.empty)
fromValueMay _ = pure Nothing
fromValue = fromValueMay >=> \case
Just v -> pure v
Nothing -> pure (M.empty, M.empty)
2019-03-09 19:24:14 +01:00
instance ( MonadFreshId Int m
, MonadAtomicRef m
, Ref m ~ STRef s
, MonadFix m
) => ToValue (AttrSet (JThunkT s m), AttrSet SourcePos) (InferT s m) (Judgment s) where
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
toValue (xs, _) = Judgment
<$> foldrM go As.empty xs
<*> (concat <$> traverse (`force` (pure . typeConstraints)) xs)
<*> (TSet True <$> traverse (`force` (pure . inferredType)) xs)
where
go x rest = force x $ \x' -> pure $ As.merge (assumptions x') rest
2019-03-09 19:24:14 +01:00
instance ( MonadFreshId Int m
, MonadAtomicRef m
, Ref m ~ STRef s
, MonadFix m
) => ToValue [JThunkT s m] (InferT s m) (Judgment s) where
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
toValue xs = Judgment
<$> foldrM go As.empty xs
<*> (concat <$> traverse (`force` (pure . typeConstraints)) xs)
<*> (TList <$> traverse (`force` (pure . inferredType)) xs)
where
go x rest = force x $ \x' -> pure $ As.merge (assumptions x') rest
2019-03-09 19:24:14 +01:00
instance ( MonadFreshId Int m
, MonadAtomicRef m
, Ref m ~ STRef s
, MonadFix m
) => ToValue Bool (InferT s m) (Judgment s) where
toValue _ = pure $ Judgment As.empty [] typeBool
2019-03-09 19:24:14 +01:00
infer :: ( MonadFreshId Int m
, MonadAtomicRef m
, Ref m ~ STRef s
, MonadFix m
) => NExpr -> InferT s m (Judgment s)
infer = cata Eval.eval
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
inferTop :: Env -> [(Text, NExpr)] -> Either InferError Env
inferTop env [] = Right env
inferTop env ((name, ex):xs) = case inferExpr env ex of
Left err -> Left err
Right ty -> inferTop (extend env (name, ty)) xs
normalize :: Scheme -> Scheme
normalize (Forall _ body) = Forall (map snd ord) (normtype body)
where
ord = zip (nub $ fv body) (map TV letters)
fv (TVar a) = [a]
2018-05-18 07:16:50 +02:00
fv (a :~> b) = fv a ++ fv b
fv (TCon _) = []
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
fv (TSet _ a) = concatMap fv (M.elems a)
fv (TList a) = concatMap fv a
fv (TMany ts) = concatMap fv ts
2018-05-18 07:16:50 +02:00
normtype (a :~> b) = normtype a :~> normtype b
normtype (TCon a) = TCon a
normtype (TSet b a) = TSet b (M.map normtype a)
normtype (TList a) = TList (map normtype a)
normtype (TMany ts) = TMany (map normtype ts)
normtype (TVar a) =
case Prelude.lookup a ord of
Just x -> TVar x
Nothing -> error "type variable not in signature"
-------------------------------------------------------------------------------
-- Constraint Solver
-------------------------------------------------------------------------------
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
newtype Solver m a = Solver (LogicT (StateT [TypeError] m) a)
deriving (Functor, Applicative, Alternative, Monad, MonadPlus,
MonadLogic, MonadState [TypeError])
instance MonadTrans Solver where
lift = Solver . lift . lift
instance Monad m => MonadError TypeError (Solver m) where
throwError err = Solver $ lift (modify (err:)) >> mzero
catchError _ _ = error "This is never used"
runSolver :: Monad m => Solver m a -> m (Either [TypeError] [a])
runSolver (Solver s) = do
res <- runStateT (observeAllT s) []
pure $ case res of
(x:xs, _) -> Right (x:xs)
(_, es) -> Left (nub es)
-- | The empty substitution
emptySubst :: Subst
emptySubst = mempty
-- | Compose substitutions
compose :: Subst -> Subst -> Subst
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
Subst s1 `compose` Subst s2 =
Subst $ Map.map (apply (Subst s1)) s2 `Map.union` s1
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
unifyMany :: Monad m => [Type] -> [Type] -> Solver m Subst
unifyMany [] [] = return emptySubst
unifyMany (t1 : ts1) (t2 : ts2) =
do su1 <- unifies t1 t2
su2 <- unifyMany (apply su1 ts1) (apply su1 ts2)
return (su2 `compose` su1)
unifyMany t1 t2 = throwError $ UnificationMismatch t1 t2
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
allSameType :: [Type] -> Bool
allSameType [] = True
allSameType [_] = True
allSameType (x:y:ys) = x == y && allSameType (y:ys)
unifies :: Monad m => Type -> Type -> Solver m Subst
unifies t1 t2 | t1 == t2 = return emptySubst
unifies (TVar v) t = v `bind` t
unifies t (TVar v) = v `bind` t
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
unifies (TList xs) (TList ys)
| allSameType xs && allSameType ys = case (xs, ys) of
(x:_, y:_) -> unifies x y
_ -> return emptySubst
| length xs == length ys = unifyMany xs ys
-- We assume that lists of different lengths containing various types cannot
-- be unified.
unifies t1@(TList _) t2@(TList _) = throwError $ UnificationFail t1 t2
unifies (TSet True _) (TSet True _) = return emptySubst
unifies (TSet False b) (TSet True s)
| M.keys b `intersect` M.keys s == M.keys s = return emptySubst
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
unifies (TSet True s) (TSet False b)
| M.keys b `intersect` M.keys s == M.keys b = return emptySubst
unifies (TSet False s) (TSet False b)
| null (M.keys b \\ M.keys s) = return emptySubst
2018-05-18 07:16:50 +02:00
unifies (t1 :~> t2) (t3 :~> t4) = unifyMany [t1, t2] [t3, t4]
unifies (TMany t1s) t2 = considering t1s >>- unifies ?? t2
unifies t1 (TMany t2s) = considering t2s >>- unifies t1
unifies t1 t2 = throwError $ UnificationFail t1 t2
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
bind :: Monad m => TVar -> Type -> Solver m Subst
bind a t | t == TVar a = return emptySubst
| occursCheck a t = throwError $ InfiniteType a t
| otherwise = return (Subst $ Map.singleton a t)
occursCheck :: FreeTypeVars a => TVar -> a -> Bool
occursCheck a t = a `Set.member` ftv t
nextSolvable :: [Constraint] -> (Constraint, [Constraint])
nextSolvable xs = fromJust (find solvable (chooseOne xs))
where
chooseOne xs = [(x, ys) | x <- xs, let ys = delete x xs]
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
solvable (EqConst{}, _) = True
solvable (ExpInstConst{}, _) = True
solvable (ImpInstConst _t1 ms t2, cs) =
Set.null ((ftv t2 `Set.difference` ms) `Set.intersection` atv cs)
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
considering :: [a] -> Solver m a
considering xs = Solver $ LogicT $ \c n -> foldr c n xs
solve :: MonadState InferState m => [Constraint] -> Solver m Subst
solve [] = return emptySubst
solve cs = solve' (nextSolvable cs)
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
where
solve' (EqConst t1 t2, cs) =
unifies t1 t2 >>- \su1 ->
solve (apply su1 cs) >>- \su2 ->
return (su2 `compose` su1)
Much more work on type inference; report multiple possible types This is needed because of a function like this: x: y: x + y This is polymorphic, but over exactly four possibilities: int -> int int -> float float -> int float -> float The "type" is really the combination of the four, since we don't yet have a mechanism like type classes, which could have rendered this as a single type: (Num a, Num b) => (x :: a): (y :: b): x + y Going this route will require that we manage an implicit type classes hierarchy, and perform unification on constraints as well as types. In the interim, I just lifted the unification algorithm into the LogicT monad, and use back-tracking search to find all the possible types an expression could be. The main problem with using LogicT, however, is that there are many types it *couldn't* be, and in the case of a unification failure, it not yet clear what the type should have been. For example: "foo" + 2 Should the string have been a float or an integer or a path? Or should the integer have been a string? So for now we report all the possibilities, since it's not obvious which part of the expression is in error: hnix: Type error: TypeInferenceErrors [ UnificationFail (TCon "integer") (TCon "string") , UnificationFail (TCon "string") (TCon "path") , UnificationFail (TCon "string") (TCon "float") , UnificationFail (TCon "string") (TCon "integer") ] This is a case where enumerating types rather than trying to make them compact using type classes might actually be an improvement, since the errors here would have been only slightly less numerous: string != Num a => a string != path integer != string Clearly a better reporting mechanism is needed to clarify these problems. I can imagine that in an IDE, there would be a squiggly under both sides of the expression, each suggesting the type that was expected for that argument under the assumption that the other argument (the one not be inspected) was the correct one.
2018-05-02 02:35:13 +02:00
solve' (ImpInstConst t1 ms t2, cs) =
solve (ExpInstConst t1 (generalize ms t2) : cs)
solve' (ExpInstConst t s, cs) = do
s' <- lift $ instantiate s
solve (EqConst t s' : cs)
2018-11-17 22:21:03 +01:00
2019-03-09 22:48:55 +01:00
instance Monad m => Scoped (JThunkT s m) (InferT s m) where
2018-11-17 22:21:03 +01:00
currentScopes = currentScopesReader
2019-03-09 19:24:14 +01:00
clearScopes = clearScopesReader @(InferT s m) @(JThunkT s m)
2018-11-17 22:21:03 +01:00
pushScopes = pushScopesReader
lookupVar = lookupVarReader