2019-03-17 00:23:40 +01:00
|
|
|
{-# LANGUAGE AllowAmbiguousTypes #-}
|
|
|
|
{-# LANGUAGE ConstraintKinds #-}
|
2018-05-01 03:47:58 +02:00
|
|
|
{-# LANGUAGE ExistentialQuantification #-}
|
|
|
|
{-# LANGUAGE FlexibleContexts #-}
|
2018-04-12 19:53:50 +02:00
|
|
|
{-# LANGUAGE FlexibleInstances #-}
|
2018-05-01 03:47:58 +02:00
|
|
|
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
2018-04-12 19:53:50 +02:00
|
|
|
{-# LANGUAGE LambdaCase #-}
|
2018-05-01 03:47:58 +02:00
|
|
|
{-# 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 #-}
|
2018-05-01 03:47:58 +02:00
|
|
|
{-# LANGUAGE StandaloneDeriving #-}
|
2018-05-02 22:46:32 +02:00
|
|
|
{-# 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 #-}
|
2018-04-12 19:53:50 +02:00
|
|
|
{-# LANGUAGE TypeSynonymInstances #-}
|
|
|
|
|
2019-03-09 23:10:48 +01:00
|
|
|
{-# OPTIONS_GHC -Wno-name-shadowing #-}
|
2018-04-12 19:53:50 +02:00
|
|
|
|
2019-03-17 22:47:38 +01:00
|
|
|
module Nix.Type.Infer
|
|
|
|
( Constraint(..)
|
|
|
|
, TypeError(..)
|
|
|
|
, InferError(..)
|
|
|
|
, Subst(..)
|
|
|
|
, inferTop
|
|
|
|
)
|
|
|
|
where
|
2018-04-12 19:53:50 +02:00
|
|
|
|
2018-05-01 03:47:58 +02:00
|
|
|
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
|
2018-04-12 19:53:50 +02:00
|
|
|
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
|
2018-04-12 19:53:50 +02:00
|
|
|
import Control.Monad.Reader
|
2018-11-16 20:04:14 +01:00
|
|
|
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
|
2018-04-12 19:53:50 +02:00
|
|
|
import Data.Fix
|
2018-05-01 03:47:58 +02:00
|
|
|
import Data.Foldable
|
2019-03-17 22:47:38 +01: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 )
|
|
|
|
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
|
2019-03-17 22:47:38 +01:00
|
|
|
import Nix.Eval ( MonadEval(..) )
|
|
|
|
import qualified Nix.Eval as 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
|
|
|
import Nix.Expr.Types
|
|
|
|
import Nix.Expr.Types.Annotated
|
2019-03-10 16:47:10 +01:00
|
|
|
import Nix.Fresh
|
2018-09-23 22:03:44 +02:00
|
|
|
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
|
2019-03-11 16:04:15 +01:00
|
|
|
import Nix.Thunk.Basic
|
2019-03-17 22:47:38 +01:00
|
|
|
import qualified Nix.Type.Assumption as As
|
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.Type.Env
|
2019-03-17 22:47:38 +01:00
|
|
|
import qualified Nix.Type.Env as Env
|
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.Type.Type
|
|
|
|
import Nix.Utils
|
2019-03-16 20:48:07 +01:00
|
|
|
import Nix.Var
|
2018-04-12 19:53:50 +02:00
|
|
|
|
|
|
|
-------------------------------------------------------------------------------
|
|
|
|
-- Classes
|
|
|
|
-------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
-- | Inference monad
|
2019-03-09 19:24:14 +01:00
|
|
|
newtype InferT s m a = InferT
|
2018-05-01 03:47:58 +02:00
|
|
|
{ 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
|
2018-05-01 03:47:58 +02:00
|
|
|
}
|
2019-03-17 00:23:40 +01:00
|
|
|
deriving
|
|
|
|
( Functor
|
|
|
|
, Applicative
|
|
|
|
, Alternative
|
|
|
|
, Monad
|
|
|
|
, MonadPlus
|
|
|
|
, MonadFix
|
|
|
|
, MonadReader (Set.Set TVar, Scopes (InferT s m) (JThunkT s m))
|
|
|
|
, MonadFail
|
|
|
|
, MonadState InferState
|
|
|
|
, MonadError InferError
|
|
|
|
)
|
|
|
|
|
|
|
|
instance MonadTrans (InferT s) where
|
2019-03-17 22:47:38 +01:00
|
|
|
lift = InferT . lift . lift . lift
|
2019-03-17 00:23:40 +01:00
|
|
|
|
|
|
|
instance MonadThunkId m => MonadThunkId (InferT s m) where
|
2019-03-17 22:47:38 +01:00
|
|
|
type ThunkId (InferT s m) = ThunkId m
|
2018-04-12 19:53:50 +02:00
|
|
|
|
|
|
|
-- | Inference state
|
|
|
|
newtype InferState = InferState { count :: Int }
|
|
|
|
|
|
|
|
-- | Initial inference state
|
|
|
|
initInfer :: InferState
|
|
|
|
initInfer = InferState { count = 0 }
|
|
|
|
|
2018-05-01 03:47:58 +02:00
|
|
|
data Constraint
|
|
|
|
= EqConst Type Type
|
|
|
|
| ExpInstConst Type Scheme
|
|
|
|
| ImpInstConst Type (Set.Set TVar) Type
|
|
|
|
deriving (Show, Eq, Ord)
|
2018-04-12 19:53:50 +02:00
|
|
|
|
2018-05-01 03:47:58 +02:00
|
|
|
newtype Subst = Subst (Map TVar Type)
|
2018-04-24 01:39:37 +02:00
|
|
|
deriving (Eq, Ord, Show, Semigroup, Monoid)
|
2018-04-12 19:53:50 +02:00
|
|
|
|
|
|
|
class Substitutable a where
|
|
|
|
apply :: Subst -> a -> a
|
|
|
|
|
|
|
|
instance Substitutable TVar where
|
|
|
|
apply (Subst s) a = tv
|
2019-03-17 22:47:38 +01:00
|
|
|
where
|
|
|
|
t = TVar a
|
|
|
|
(TVar tv) = Map.findWithDefault t a s
|
2018-04-12 19:53:50 +02:00
|
|
|
|
|
|
|
instance Substitutable Type where
|
2019-03-17 22:47:38 +01:00
|
|
|
apply _ ( TCon a ) = TCon a
|
|
|
|
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
|
|
|
|
apply s ( t1 :~> t2) = apply s t1 :~> apply s t2
|
|
|
|
apply s ( TMany ts ) = TMany (map (apply s) ts)
|
2018-04-12 19:53:50 +02:00
|
|
|
|
|
|
|
instance Substitutable Scheme where
|
2018-05-01 03:47:58 +02:00
|
|
|
apply (Subst s) (Forall as t) = Forall as $ apply s' t
|
|
|
|
where s' = Subst $ foldr Map.delete s as
|
2018-04-12 19:53:50 +02:00
|
|
|
|
|
|
|
instance Substitutable Constraint where
|
2019-03-17 22:47:38 +01:00
|
|
|
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)
|
2018-04-12 19:53:50 +02:00
|
|
|
|
|
|
|
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
|
2019-03-17 22:47:38 +01:00
|
|
|
ftv (TVar a ) = Set.singleton a
|
|
|
|
ftv (TSet _ a ) = Set.unions (map ftv (M.elems a))
|
|
|
|
ftv (TList a ) = Set.unions (map ftv a)
|
2018-05-18 07:16:50 +02:00
|
|
|
ftv (t1 :~> t2) = ftv t1 `Set.union` ftv t2
|
2019-03-17 22:47:38 +01:00
|
|
|
ftv (TMany ts ) = Set.unions (map ftv ts)
|
2018-04-12 19:53:50 +02:00
|
|
|
|
|
|
|
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
|
2019-03-17 22:47:38 +01:00
|
|
|
ftv = foldr (Set.union . ftv) Set.empty
|
2018-04-12 19:53:50 +02:00
|
|
|
|
|
|
|
instance (Ord a, FreeTypeVars a) => FreeTypeVars (Set.Set a) where
|
2019-03-17 22:47:38 +01:00
|
|
|
ftv = foldr (Set.union . ftv) Set.empty
|
2018-04-12 19:53:50 +02:00
|
|
|
|
|
|
|
|
|
|
|
class ActiveTypeVars a where
|
|
|
|
atv :: a -> Set.Set TVar
|
|
|
|
|
|
|
|
instance ActiveTypeVars Constraint where
|
2019-03-17 22:47:38 +01:00
|
|
|
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
|
2018-04-12 19:53:50 +02:00
|
|
|
|
|
|
|
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]
|
2018-04-12 19:53:50 +02:00
|
|
|
| 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]
|
2018-05-01 03:47:58 +02:00
|
|
|
|
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
|
2018-05-01 03:47:58 +02:00
|
|
|
|
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
|
2019-03-17 22:47:38 +01:00
|
|
|
x <> _ = x
|
2018-05-01 03:47:58 +02:00
|
|
|
|
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
|
2019-03-17 22:47:38 +01:00
|
|
|
mempty = TypeInferenceAborted
|
|
|
|
mappend = (<>)
|
2018-04-12 19:53:50 +02:00
|
|
|
|
|
|
|
-------------------------------------------------------------------------------
|
|
|
|
-- Inference
|
|
|
|
-------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
-- | Run the inference monad
|
2019-03-17 00:23:40 +01:00
|
|
|
runInfer' :: MonadInfer m => InferT s m a -> m (Either InferError a)
|
2019-03-17 22:47:38 +01:00
|
|
|
runInfer' =
|
|
|
|
runExceptT
|
|
|
|
. (`evalStateT` initInfer)
|
|
|
|
. (`runReaderT` (Set.empty, emptyScopes))
|
|
|
|
. getInfer
|
2018-04-12 19:53:50 +02:00
|
|
|
|
2019-03-17 22:47:38 +01:00
|
|
|
runInfer :: (forall s . InferT s (FreshIdT Int (ST s)) a) -> Either InferError a
|
2019-03-16 20:48:07 +01:00
|
|
|
runInfer m = runST $ do
|
2019-03-17 22:47:38 +01:00
|
|
|
i <- newVar (1 :: Int)
|
|
|
|
runFreshIdT i (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-17 22:47:38 +01:00
|
|
|
inferType
|
|
|
|
:: forall s m . MonadInfer m => Env -> NExpr -> InferT s m [(Subst, Type)]
|
2018-04-12 19:53:50 +02:00
|
|
|
inferType env ex = do
|
2018-05-01 03:47:58 +02:00
|
|
|
Judgment as cs t <- infer ex
|
2019-03-17 22:47:38 +01: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
|
|
|
|
]
|
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
|
|
|
inferState <- get
|
|
|
|
let eres = (`evalState` inferState) $ runSolver $ do
|
2019-03-17 22:47:38 +01:00
|
|
|
subst <- solve (cs ++ cs')
|
|
|
|
return (subst, subst `apply` 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
|
|
|
case eres of
|
2019-03-17 22:47:38 +01:00
|
|
|
Left errs -> throwError $ TypeInferenceErrors errs
|
|
|
|
Right xs -> pure xs
|
2018-04-12 19:53:50 +02:00
|
|
|
|
|
|
|
-- | 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]
|
2018-04-12 19:53:50 +02:00
|
|
|
inferExpr env ex = case runInfer (inferType env ex) of
|
2019-03-17 22:47:38 +01:00
|
|
|
Left err -> Left err
|
|
|
|
Right xs -> Right $ map (\(subst, ty) -> closeOver (subst `apply` ty)) xs
|
2018-04-12 19:53:50 +02:00
|
|
|
|
|
|
|
-- | 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
|
2018-04-12 19:53:50 +02:00
|
|
|
|
|
|
|
letters :: [String]
|
2019-03-17 22:47:38 +01:00
|
|
|
letters = [1 ..] >>= flip replicateM ['a' .. 'z']
|
2018-04-12 19:53:50 +02:00
|
|
|
|
2019-03-10 03:40:59 +01:00
|
|
|
freshTVar :: MonadState InferState m => m TVar
|
|
|
|
freshTVar = do
|
2019-03-17 22:47:38 +01:00
|
|
|
s <- get
|
|
|
|
put s { count = count s + 1 }
|
|
|
|
return $ TV (letters !! count s)
|
2019-03-10 03:40:59 +01:00
|
|
|
|
|
|
|
fresh :: MonadState InferState m => m Type
|
|
|
|
fresh = TVar <$> freshTVar
|
2018-04-12 19:53:50 +02:00
|
|
|
|
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
|
2018-04-12 19:53:50 +02:00
|
|
|
instantiate (Forall as t) = do
|
2019-03-17 22:47:38 +01:00
|
|
|
as' <- mapM (const fresh) as
|
|
|
|
let s = Subst $ Map.fromList $ zip as as'
|
|
|
|
return $ apply s t
|
2018-04-12 19:53:50 +02:00
|
|
|
|
|
|
|
generalize :: Set.Set TVar -> Type -> Scheme
|
2019-03-17 22:47:38 +01:00
|
|
|
generalize free t = Forall as t
|
|
|
|
where as = Set.toList $ ftv t `Set.difference` free
|
2018-04-12 19:53:50 +02:00
|
|
|
|
2018-05-01 03:47:58 +02:00
|
|
|
unops :: Type -> NUnaryOp -> [Constraint]
|
|
|
|
unops u1 = \case
|
2019-03-17 22:47:38 +01:00
|
|
|
NNot -> [EqConst u1 (typeFun [typeBool, typeBool])]
|
|
|
|
NNeg ->
|
|
|
|
[ EqConst
|
|
|
|
u1
|
|
|
|
(TMany [typeFun [typeInt, typeInt], typeFun [typeFloat, typeFloat]])
|
|
|
|
]
|
2018-05-01 03:47:58 +02:00
|
|
|
|
|
|
|
binops :: Type -> NBinaryOp -> [Constraint]
|
|
|
|
binops u1 = \case
|
2019-03-17 22:47:38 +01:00
|
|
|
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]
|
|
|
|
]
|
|
|
|
)
|
|
|
|
]
|
2018-05-01 03:47:58 +02:00
|
|
|
|
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-17 00:23:40 +01:00
|
|
|
instance MonadRef m => MonadRef (InferT s m) where
|
2019-03-17 22:47:38 +01:00
|
|
|
type Ref (InferT s m) = Ref m
|
|
|
|
newRef x = liftInfer $ newRef x
|
|
|
|
readRef x = liftInfer $ readRef x
|
|
|
|
writeRef x y = liftInfer $ writeRef x y
|
2018-11-16 20:04:14 +01:00
|
|
|
|
2019-03-17 00:23:40 +01:00
|
|
|
instance MonadAtomicRef m => MonadAtomicRef (InferT s m) where
|
2019-03-17 22:47:38 +01:00
|
|
|
atomicModifyRef x f = liftInfer $ do
|
|
|
|
res <- snd . f <$> readRef x
|
|
|
|
_ <- modifyRef x (fst . f)
|
|
|
|
return res
|
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-11 16:04:15 +01:00
|
|
|
newtype JThunkT s m = JThunk (NThunkF (InferT s m) (Judgment s))
|
2018-05-01 03:47:58 +02:00
|
|
|
|
2019-03-09 19:24:14 +01:00
|
|
|
instance Monad m => MonadThrow (InferT s m) where
|
2019-03-17 22:47:38 +01:00
|
|
|
throwM = 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
|
|
|
|
2019-03-09 19:24:14 +01:00
|
|
|
instance Monad m => MonadCatch (InferT s m) where
|
2019-03-17 22:47:38 +01: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
|
|
|
|
|
|
|
|
type MonadInfer m = (MonadThunkId m, MonadVar m, MonadFix m)
|
2019-03-17 00:23:40 +01:00
|
|
|
|
|
|
|
instance MonadInfer m
|
|
|
|
=> MonadThunk (JThunkT s m) (InferT s m) (Judgment s) where
|
2019-03-17 22:47:38 +01:00
|
|
|
thunk = fmap JThunk . thunk
|
|
|
|
thunkId (JThunk x) = thunkId x
|
2018-05-03 00:07:30 +02:00
|
|
|
|
2019-03-17 22:47:38 +01:00
|
|
|
query (JThunk x) b f = query x b f
|
|
|
|
queryM (JThunk x) b f = queryM x b f
|
2019-03-14 23:10:41 +01:00
|
|
|
|
2019-03-17 22:47:38 +01:00
|
|
|
force (JThunk t) f = catch (force 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 (forceEff 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
|
|
|
|
2019-03-17 22:47:38 +01:00
|
|
|
wrapValue = JThunk . wrapValue
|
|
|
|
getValue (JThunk x) = getValue 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-17 00:23:40 +01:00
|
|
|
instance MonadInfer m => MonadEval (Judgment s) (InferT s m) where
|
2019-03-17 22:47:38 +01:00
|
|
|
freeVariable var = do
|
|
|
|
tv <- fresh
|
|
|
|
return $ Judgment (As.singleton var tv) [] tv
|
|
|
|
|
|
|
|
synHole var = do
|
|
|
|
tv <- fresh
|
|
|
|
return $ Judgment (As.singleton var tv) [] tv
|
|
|
|
|
|
|
|
-- If we fail to look up an attribute, we just don't know the type.
|
|
|
|
attrMissing _ _ = Judgment As.empty [] <$> fresh
|
|
|
|
|
|
|
|
evaledSym _ = pure
|
|
|
|
|
|
|
|
evalCurPos = 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
|
|
|
|
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)
|
|
|
|
(cs1 ++ cs2 ++ binops (t1 :~> t2 :~> tv) op)
|
|
|
|
tv
|
|
|
|
|
|
|
|
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)
|
|
|
|
(cs1 ++ cs2 ++ [EqConst t1 (t2 :~> tv)])
|
|
|
|
tv
|
|
|
|
|
|
|
|
evalAbs (Param x) k = do
|
|
|
|
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 ])
|
|
|
|
(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 ])
|
|
|
|
(ty :~> t)
|
|
|
|
|
|
|
|
evalError = throwError . EvaluationError
|
2018-05-01 03:47:58 +02:00
|
|
|
|
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
|
2018-05-01 03:47:58 +02:00
|
|
|
{ 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
|
2019-03-17 22:47:38 +01:00
|
|
|
fromValueMay _ = return Nothing
|
|
|
|
fromValue _ = error "Unused"
|
2018-05-01 03:47:58 +02:00
|
|
|
|
2019-03-17 00:23:40 +01:00
|
|
|
instance MonadInfer m
|
|
|
|
=> FromValue (AttrSet (JThunkT s m), AttrSet SourcePos)
|
|
|
|
(InferT s m) (Judgment s) where
|
2019-03-17 22:47:38 +01: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)
|
2018-05-01 03:47:58 +02:00
|
|
|
|
2019-03-17 00:23:40 +01:00
|
|
|
instance MonadInfer m
|
|
|
|
=> ToValue (AttrSet (JThunkT s m), AttrSet SourcePos)
|
|
|
|
(InferT s m) (Judgment s) where
|
2019-03-17 22:47:38 +01: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
|
2018-05-01 03:47:58 +02:00
|
|
|
|
2019-03-17 00:23:40 +01:00
|
|
|
instance MonadInfer m => ToValue [JThunkT s m] (InferT s m) (Judgment s) where
|
2019-03-17 22:47:38 +01: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
|
2018-05-01 03:47:58 +02:00
|
|
|
|
2019-03-17 00:23:40 +01:00
|
|
|
instance MonadInfer m => ToValue Bool (InferT s m) (Judgment s) where
|
2019-03-17 22:47:38 +01:00
|
|
|
toValue _ = pure $ Judgment As.empty [] typeBool
|
2018-05-01 03:47:58 +02:00
|
|
|
|
2019-03-17 00:23:40 +01:00
|
|
|
infer :: MonadInfer m => NExpr -> InferT s m (Judgment s)
|
2018-05-01 03:47:58 +02:00
|
|
|
infer = cata Eval.eval
|
2018-04-12 19:53:50 +02:00
|
|
|
|
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
|
2019-03-17 22:47:38 +01:00
|
|
|
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
|
2018-04-12 19:53:50 +02:00
|
|
|
|
|
|
|
normalize :: Scheme -> Scheme
|
|
|
|
normalize (Forall _ body) = Forall (map snd ord) (normtype body)
|
2019-03-17 22:47:38 +01:00
|
|
|
where
|
|
|
|
ord = zip (nub $ fv body) (map TV letters)
|
|
|
|
|
|
|
|
fv (TVar a ) = [a]
|
|
|
|
fv (a :~> b ) = fv a ++ fv b
|
|
|
|
fv (TCon _ ) = []
|
|
|
|
fv (TSet _ a) = concatMap fv (M.elems a)
|
|
|
|
fv (TList a ) = concatMap fv a
|
|
|
|
fv (TMany ts) = concatMap fv ts
|
|
|
|
|
|
|
|
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"
|
2018-04-12 19:53:50 +02:00
|
|
|
|
|
|
|
-------------------------------------------------------------------------------
|
|
|
|
-- 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
|
2019-03-17 22:47:38 +01:00
|
|
|
lift = Solver . 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
|
|
|
|
|
|
|
instance Monad m => MonadError TypeError (Solver m) where
|
2019-03-17 22:47:38 +01:00
|
|
|
throwError err = Solver $ lift (modify (err :)) >> mzero
|
|
|
|
catchError _ _ = error "This is never used"
|
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
|
|
|
|
|
|
|
runSolver :: Monad m => Solver m a -> m (Either [TypeError] [a])
|
|
|
|
runSolver (Solver s) = do
|
2019-03-17 22:47:38 +01:00
|
|
|
res <- runStateT (observeAllT s) []
|
|
|
|
pure $ case res of
|
|
|
|
(x : xs, _ ) -> Right (x : xs)
|
|
|
|
(_ , es) -> Left (nub es)
|
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
|
|
|
|
2018-04-12 19:53:50 +02:00
|
|
|
-- | 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 =
|
2019-03-17 22:47:38 +01:00
|
|
|
Subst $ Map.map (apply (Subst s1)) s2 `Map.union` s1
|
2018-04-12 19:53:50 +02:00
|
|
|
|
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
|
2019-03-17 22:47:38 +01:00
|
|
|
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)
|
2018-04-12 19:53:50 +02:00
|
|
|
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
|
2019-03-17 22:47:38 +01:00
|
|
|
allSameType [] = True
|
|
|
|
allSameType [_ ] = True
|
|
|
|
allSameType (x : y : ys) = x == y && allSameType (y : ys)
|
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 :: Monad m => Type -> Type -> Solver m Subst
|
2019-03-17 22:47:38 +01:00
|
|
|
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)
|
2019-03-17 22:47:38 +01:00
|
|
|
| allSameType xs && allSameType ys = case (xs, ys) of
|
|
|
|
(x : _, y : _) -> unifies x y
|
|
|
|
_ -> return emptySubst
|
|
|
|
| length xs == length ys = unifyMany xs ys
|
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
|
|
|
-- We assume that lists of different lengths containing various types cannot
|
|
|
|
-- be unified.
|
2019-03-17 22:47:38 +01:00
|
|
|
unifies t1@(TList _ ) t2@(TList _ ) = throwError $ UnificationFail t1 t2
|
|
|
|
unifies ( TSet True _) ( TSet True _) = 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 False b) (TSet True s)
|
2019-03-17 22:47:38 +01:00
|
|
|
| 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)
|
2019-03-17 22:47:38 +01:00
|
|
|
| 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]
|
2019-03-17 22:47:38 +01:00
|
|
|
unifies (TMany t1s) t2 = considering t1s >>- unifies ?? t2
|
|
|
|
unifies t1 (TMany t2s) = considering t2s >>- unifies t1
|
|
|
|
unifies t1 t2 = throwError $ UnificationFail t1 t2
|
2018-04-12 19:53:50 +02:00
|
|
|
|
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
|
2018-04-12 19:53:50 +02:00
|
|
|
bind a t | t == TVar a = return emptySubst
|
|
|
|
| occursCheck a t = throwError $ InfiniteType a t
|
|
|
|
| otherwise = return (Subst $ Map.singleton a t)
|
|
|
|
|
2019-03-17 22:47:38 +01:00
|
|
|
occursCheck :: FreeTypeVars a => TVar -> a -> Bool
|
2018-04-12 19:53:50 +02:00
|
|
|
occursCheck a t = a `Set.member` ftv t
|
|
|
|
|
|
|
|
nextSolvable :: [Constraint] -> (Constraint, [Constraint])
|
|
|
|
nextSolvable xs = fromJust (find solvable (chooseOne xs))
|
2019-03-17 22:47:38 +01:00
|
|
|
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
|
|
|
|
2019-03-17 22:47:38 +01:00
|
|
|
solvable (EqConst{} , _) = True
|
|
|
|
solvable (ExpInstConst{}, _) = True
|
|
|
|
solvable (ImpInstConst _t1 ms t2, cs) =
|
|
|
|
Set.null ((ftv t2 `Set.difference` ms) `Set.intersection` atv cs)
|
2018-04-12 19:53:50 +02:00
|
|
|
|
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
|
2018-04-12 19:53:50 +02:00
|
|
|
solve [] = return emptySubst
|
|
|
|
solve cs = solve' (nextSolvable cs)
|
2019-03-17 22:47:38 +01: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
|
|
|
|
2019-03-17 22:47:38 +01:00
|
|
|
solve' (ImpInstConst t1 ms t2, cs) =
|
|
|
|
solve (ExpInstConst t1 (generalize ms t2) : 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
|
|
|
|
2019-03-17 22:47:38 +01:00
|
|
|
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-17 22:47:38 +01:00
|
|
|
clearScopes = clearScopesReader @(InferT s m) @(JThunkT s m)
|
|
|
|
pushScopes = pushScopesReader
|
|
|
|
lookupVar = lookupVarReader
|