@@ -11,8 +11,10 @@ module Language.PureScript.TypeChecker.Roles
1111import Prelude.Compat
1212
1313import Data.Coerce (coerce )
14+ import Data.Foldable (fold )
1415import qualified Data.Map as M
1516import Data.Maybe (fromMaybe )
17+ import Data.Monoid (Endo (.. ))
1618import qualified Data.Set as S
1719import Data.Text (Text )
1820
@@ -27,7 +29,7 @@ import Language.PureScript.Types
2729-- ascribed to any given variable, as defined by the @Role@ type's @Ord@
2830-- instance. That is, a variable that has been marked as @Nominal@ can not
2931-- later be marked @Representational@, and so on.
30- newtype RoleMap = RoleMap { getRoleMap :: M. Map Text Role }
32+ newtype RoleMap = RoleMap ( M. Map Text Role )
3133
3234instance Semigroup RoleMap where
3335 (<>) =
@@ -37,46 +39,65 @@ instance Monoid RoleMap where
3739 mempty =
3840 RoleMap M. empty
3941
42+ type RoleEnv = M. Map (Qualified (ProperName 'TypeName)) [Role ]
43+
4044-- |
4145-- Given an environment and the qualified name of a type constructor in that
4246-- environment, returns a list of roles, in the order they are defined in the
4347-- type definition.
4448inferRoles :: Environment -> Qualified (ProperName 'TypeName) -> [Role ]
45- inferRoles env tyName
46- | Just roles <- M. lookup tyName (roleDeclarations env) =
47- roles
48- | Just (_, DataType tvs ctors) <- envMeta =
49+ inferRoles env tyName =
50+ fold . M. lookup tyName $ updateRoleEnv env tyName (roleDeclarations env)
51+
52+ updateRoleEnv :: Environment -> Qualified (ProperName 'TypeName) -> RoleEnv -> RoleEnv
53+ updateRoleEnv env tyName initialRoleEnv =
54+ case M. lookup tyName (types env) of
55+ Nothing ->
56+ initialRoleEnv
57+ Just ty ->
58+ case updateRoleEnvOnce tyName ty initialRoleEnv of
59+ (updatedRoleEnv, dependencies)
60+ | null dependencies ->
61+ updatedRoleEnv
62+ | otherwise ->
63+ let initialRoleEnv' = foldMap (Endo . updateRoleEnv env) dependencies
64+ `appEndo` updatedRoleEnv
65+ in updateRoleEnv env tyName $ initialRoleEnv'
66+
67+ updateRoleEnvOnce :: Qualified (ProperName 'TypeName) -> (SourceType , TypeKind ) -> RoleEnv -> (RoleEnv , S. Set (Qualified (ProperName 'TypeName)))
68+ updateRoleEnvOnce tyName ty roleEnv
69+ | (_, DataType tvs ctors) <- ty =
4970 -- A plain data type. For each constructor the type has, walk its list of
5071 -- field types and accumulate a list of (formal parameter name, role)
5172 -- pairs. Then, walk the list of defined parameters, ensuring both that
5273 -- every parameter appears (with a default role of phantom) and that they
5374 -- appear in the right order.
54- let ctorRoles = getRoleMap $ foldMap (foldMap (walk mempty ) . snd ) ctors
55- in map (\ (tv, _) -> fromMaybe Phantom (M. lookup tv ctorRoles)) tvs
56- | Just (k, ExternData ) <- envMeta =
75+ let (RoleMap ctorsRoles, dependencies) = foldMap (foldMap (walk mempty roleEnv) . snd ) ctors
76+ roles = map (\ (tv, _) -> fromMaybe Phantom (M. lookup tv ctorsRoles)) tvs
77+ in (updateRoles tyName roles roleEnv, dependencies)
78+ | (k, ExternData ) <- ty
79+ , Nothing <- M. lookup tyName roleEnv =
5780 -- A foreign data type. Since the type will have no defined constructors
5881 -- nor associated data types, infer the set of type parameters from its
5982 -- kind and assume in the absence of role signatures that all such
6083 -- parameters are nominal.
61- rolesFromForeignTypeKind k
84+ (updateRoles tyName ( rolesFromForeignTypeKind k) roleEnv, mempty )
6285 | otherwise =
63- []
86+ (roleEnv, mempty )
6487 where
65- envTypes = types env
66- envMeta = M. lookup tyName envTypes
6788 -- This function is named walk to match the specification given in the "Role
6889 -- inference" section of the paper "Safe Zero-cost Coercions for Haskell".
69- walk :: S. Set Text -> SourceType -> RoleMap
70- walk btvs (TypeVar _ v)
90+ walk :: S. Set Text -> RoleEnv -> SourceType -> ( RoleMap , S. Set ( Qualified ( ProperName 'TypeName)))
91+ walk btvs _ (TypeVar _ v)
7192 -- A type variable standing alone (e.g. @a@ in @data D a b = D a@) is
7293 -- representational, _unless_ it has been bound by a quantifier, in which
7394 -- case it is not actually a parameter to the type (e.g. @z@ in
7495 -- @data T z = T (forall z. z -> z)@).
7596 | S. member v btvs =
7697 mempty
7798 | otherwise =
78- RoleMap $ M. singleton v Representational
79- walk btvs (ForAll _ tv _ t _) =
99+ ( RoleMap $ M. singleton v Representational , mempty )
100+ walk btvs roleEnv' (ForAll _ tv _ t _) =
80101 -- We can walk under universal quantifiers as long as we make note of the
81102 -- variables that they bind. For instance, given a definition
82103 -- @data T z = T (forall z. z -> z)@, we will make note that @z@ is bound
@@ -85,15 +106,15 @@ inferRoles env tyName
85106 -- @data D a = D (forall r. r -> a)@, we'll mark @r@ as bound so that it
86107 -- doesn't appear as a spurious parameter to @D@ when we complete
87108 -- inference.
88- walk (S. insert tv btvs) t
89- walk btvs (RCons _ _ thead ttail) =
109+ walk (S. insert tv btvs) roleEnv' t
110+ walk btvs roleEnv' (RCons _ _ thead ttail) =
90111 -- For row types, we just walk along them and collect the results.
91- walk btvs thead <> walk btvs ttail
92- walk btvs (KindedType _ t _k) =
112+ walk btvs roleEnv' thead <> walk btvs roleEnv' ttail
113+ walk btvs roleEnv' (KindedType _ t _k) =
93114 -- For kind-annotated types, discard the annotation and recurse on the
94115 -- type beneath.
95- walk btvs t
96- walk btvs t
116+ walk btvs roleEnv' t
117+ walk btvs roleEnv' t
97118 | (t1, _, t2s) <- unapplyTypes t
98119 , not $ null t2s =
99120 case t1 of
@@ -109,31 +130,37 @@ inferRoles env tyName
109130 -- * If the role is phantom, terminate, since the argument's use of
110131 -- our parameters is unimportant.
111132 TypeConstructor _ t1Name ->
112- let t1Roles = inferRoles env t1Name
133+ let roles = M. lookup t1Name roleEnv'
134+ inferred = fromMaybe (repeat Phantom ) roles
135+ dependencies = maybe (S. singleton t1Name) mempty roles
113136 k role ti = case role of
114137 Nominal ->
115- freeNominals ti
138+ ( freeNominals ti, mempty )
116139 Representational ->
117140 go ti
118141 Phantom ->
119142 mempty
120- in mconcat (zipWith k t1Roles t2s)
143+ in mappend dependencies <$> mconcat (zipWith k inferred t2s)
121144 -- If the type is an application of any other type-level term, walk
122145 -- that term to collect its roles and mark all free variables in
123146 -- its argument as nominal.
124147 _ ->
125- go t1 <> foldMap freeNominals t2s
148+ go t1 <> ( foldMap freeNominals t2s, mempty )
126149 | otherwise =
127150 mempty
128151 where
129- go = walk btvs
152+ go = walk btvs roleEnv'
130153 -- Given a type, computes the list of free variables in that type
131154 -- (taking into account those bound in @walk@) and returns a @RoleMap@
132155 -- ascribing a nominal role to each of those variables.
133156 freeNominals x =
134157 let ftvs = filter (flip S. notMember btvs) (freeTypeVariables x)
135158 in RoleMap (M. fromList $ map (, Nominal ) ftvs)
136159
160+ updateRoles :: Qualified (ProperName 'TypeName) -> [Role ] -> RoleEnv -> RoleEnv
161+ updateRoles = M. insertWith $ \ roles' roles ->
162+ uncurry min <$> zip roles' roles
163+
137164-- |
138165-- Given the kind of a foreign type, generate a list @Nominal@ roles which, in
139166-- the absence of a role signature, provides the safest default for a type whose
0 commit comments