-- | Type inference for expressions module Statics.Expr ( tcExpr, tcExprPatt, tcLetRecBindings, ) where import Util import Util.MonadRef import qualified AST import qualified Data.Loc import Meta.Quasi import Type import Statics.Env import Statics.Error import Statics.Constraint import Statics.Coercion import Statics.InstGen import Statics.Subsume import Statics.Type import Statics.Patt import {-# SOURCE #-} Statics.Decl import Prelude () import qualified Data.Map as M import Data.IORef (IORef) tcExpr ∷ MonadConstraint tv r m ⇒ Γ tv → AST.Expr R → m (AST.Expr R, Type tv) tcExpr γ e = withTVsOf mempty γ e $ \δ → infer (request Forall Exists) δ γ e Nothing tcExprPatt ∷ MonadConstraint tv r m ⇒ Γ tv → AST.Expr R → AST.Patt R → m (AST.Expr R, [Type tv]) tcExprPatt γ e π = withTVsOf mempty γ (e, π) $ \δ → do mσ1 ← extractPattAnnot δ γ π (e', σ1) ← infer (request Forall Exists) δ γ e mσ1 (_, σs) ← tcPatt δ γ π (Just σ1) [ex|+! () |] mapM (⊏: Qu) σs return (e', σs) infer ∷ MonadConstraint tv r m ⇒ Request tv → Δ tv → Γ tv → AST.Expr R → Maybe (Type tv) → m (AST.Expr R, Type tv) infer φ0 δ γ e0 mσ0 = do traceN 1 (TraceIn ("infer", φ0, e0, mσ0)) mσ ← mapM subst mσ0 let φ = maybe id prenexFlavors mσ φ0 σ ← withLocation e0 $ case e0 of [ex| $qvid:n |] → do σ' ← maybeInstGen e0 φ γ =<< γ !.! n return ([ex| $qvid:n |], σ') -- [ex| $int:_ |] → return (e0, tyInt) [ex| $char:_ |] → return (e0, tyChar) [ex| $flo:_ |] → return (e0, tyFloat) [ex| $str:_ |] → return (e0, tyString) -- [ex| $qcid:c $opt:me |] → do -- Look up the type constructor and parameter type for the -- given data constructor tcexn ← γ !.! c (tc, mσ1) ← case tcexn of Left tc → (tc,) <$> tcCons tc !.! jname c Right mσ1 → return (tcExn, mσ1) -- Propagation: If an annotation has been passed downward, split -- it into type parameters for the type constructor. If splitting -- fails, then instantiate type variables with the right bounds -- and kinds. mσs ← splitCon mσ tc σs ← sequence [ maybe (newTVTy' (qli, variancei)) return mσi | mσi ← mσs | qli ← tcBounds tc | variancei ← tcArity tc ] -- Check whether a parameter is expected. If it isn't, then assert -- that none was given. If it is, then instantiate it using the -- propagated parameters, and propagate the instantated parameter -- type downward. Force the result to subsume the expected type. case mσ1 of Nothing → do tassert (isNothing me) [msg| In expression, nullary data constructor $q:c is applied to an argument. |] σ' ← maybeGen e0 φ γ (TyApp tc σs) return ([ex| $qcid:c |], σ') Just σ1E → do let σ1 = openTy 0 σs (elimEmptyF σ1E) case me of Just e → do (e', σ1') ← infer request δ γ e (Just σ1) σ1' ≤ σ1 σ' ← maybeGen e0 φ γ (TyApp tc σs) return ([ex| $qcid:c $e' |], σ') Nothing → do σ' ← maybeGen e0 φ γ (tyArr σ1 (TyApp tc σs)) return ([ex|+ λ x → $qcid:c x |], σ') -- [ex| let $π = $e1 in $e2 |] → do mσ1 ← extractPattAnnot δ γ π ((e1', σs), αs) ← collectTVs $ do (e1', σ1) ← infer (request Forall Exists) δ γ e1 mσ1 (_, σs) ← tcPatt δ γ π (Just σ1) e2 return (e1', σs) γ' ← γ !+! π -:*- σs (e2', σ') ← infer (request φ γ αs) δ γ' e2 mσ return ([ex| let $π = $e1' in $e2' |], σ') [ex| match $e1 with $list:cas |] → do ((e1', σ1), αs) ← collectTVs (infer request δ γ e1 Nothing) (cas', σ') ← tcMatchCases (request φ γ αs) δ γ σ1 cas mσ return ([ex| match $e1' with $list:cas' |], σ') [ex| let rec $list:bs in $e2 |] → do (bs', ns, σs) ← tcLetRecBindingsΔ δ γ bs γ' ← γ !+! ns -:*- σs (e2', σ') ← infer φ δ γ' e2 mσ return ([ex| let rec $list:bs' in $e2' |], σ') [ex| let $decl:d in $e1 |] → do (d', γ', _) ← tcDecl [AST.ident "?LetModule"] γ d (e1', σ1) ← infer request δ γ' e1 mσ σ' ← maybeInstGen e0 φ γ σ1 return ([ex| let $decl:d' in $e1' |], σ') -- [ex| ($e1, $e2) |] → do [mσ1, mσ2] ← splitCon mσ tcTuple (e1', σ1) ← infer request δ γ e1 mσ1 (e2', σ2) ← infer request δ γ e2 mσ2 σ' ← maybeGen e0 φ γ (tyTuple σ1 σ2) return ([ex| ($e1', $e2') |], σ') -- [ex| λ $π → $e |] → do [mσ1, _, mσ2] ← splitCon mσ tcFun ((σ1, σs), αs) ← collectTVs (tcPatt δ γ π mσ1 e) αs' ← filterM (isMonoType <$$> subst . fst) ((fvTy &&& tvDescr) <$> αs) γ' ← γ !+! π -:*- σs (e', σ2) ← infer (request Exists γ αs) δ γ' e mσ2 for_ αs' $ \(α, descr) → do τ ← subst α tassert (isMonoType τ) [msg| Use $descr polymorphically |] let qe = arrowQualifier γ e0 σ' ← maybeGen e0 φ γ (tyFun σ1 qe σ2) return ([ex| λ $π → $e' |], σ') -- [ex| $_ $_ |] → do let (es, e1) = AST.unfoldExApp e0 ((e0', σ), αs) ← collectTVs $ do (e1', σ1) ← infer request δ γ e1 Nothing (es', σ) ← inferApp δ γ σ1 es return (foldl' AST.exApp e1' es', σ) σ' ← maybeInstGen e0 (request φ γ αs) γ σ return (e0', σ') -- [ex| `$uid:c $opt:me1 |] → do [mσRow] ← splitCon mσ tcVariant (mσ1, _) ← splitRow mσRow c σ2 ← newTVTy (me1', σ1) ← case me1 of Nothing → return (Nothing, tyUnit) Just e1 → first Just <$> infer request δ γ e1 mσ1 σ' ← maybeGen e0 φ γ (TyApp tcVariant [TyRow c σ1 σ2]) return ([ex| `$uid:c $opt:me1' |], σ') [ex| #$uid:c $e1 |] → do [mσRow] ← splitCon mσ tcVariant (_, mσ2) ← splitRow mσRow c (e1', σ2) ← infer request δ γ e1 (tyUnOp tcVariant <$> mσ2) σ1 ← newTVTy σ2' ← newTVTy tyUnOp tcVariant σ2' =: σ2 σ' ← maybeGen e0 φ γ (tyUnOp tcVariant (TyRow c σ1 σ2')) return ([ex| #$uid:c $e1' |], σ') -- [ex| { $list:flds | $e2 } |] → do (flds', e2', σ') ← inferRecordExp False e0 φ δ γ flds e2 mσ return ([ex| { $list:flds' | $e2' } |], σ') [ex| {+ $list:flds | $e2 +} |] → do (flds', e2', σ') ← inferRecordExp True e0 φ δ γ flds e2 mσ return ([ex| {+ $list:flds' | $e2' +} |], σ') -- [ex| $e1 . $uid:u |] → do (([e1'], σ), αs) ← collectTVs $ do σField ← newTVTy σRow ← newTVTy let σSel = tyBinOp tcRecord tyAf (TyRow u σField σRow) `tyLol` σField inferApp δ γ σSel [e1] σ' ← maybeInstGen e0 (request φ γ αs) γ σ return ([ex| $e1' . $uid:u |], σ') -- [ex| $e : $annot |] → do σ ← tcType δ γ annot (e', αs) ← collectTVs . withPinnedTVs σ $ do (e', σ') ← infer request δ γ e (Just σ) σ' ≤ σ return e' σ' ← maybeGen e0 (request φ γ αs) γ σ return ([ex| $e' : $annot |], σ') [ex| $e1 :> $annot |] → do σ ← tcType δ γ annot let φ' = prenexFlavors σ request (e1', σ1) ← infer (request φ') δ γ e1 Nothing (e', αs) ← collectTVs (coerceExpression e1' σ1 σ) σ' ← maybeGen e0 (request φ γ αs) γ σ return (e', σ') -- [ex| $anti:a |] → $(AST.antifail) [ex| $antiL:a |] → $(AST.antifail) -- traceN 1 (TraceOut ("infer", σ)) return σ -- | Infer the type of a record expression. inferRecordExp ∷ MonadConstraint tv r m ⇒ Bool → AST.Expr R → Request tv → Δ tv → Γ tv → [AST.Field R] → AST.Expr R → Maybe (Type tv) → m ([AST.Field R], AST.Expr R, Type tv) inferRecordExp bqual e0 φ δ γ flds e2 mσ = do let qual = if bqual then tyAf else tyUn [_, mσRow] ← splitCon mσ tcRecord let eachFld mσRow' [fdQ| $uid:ui = $ei |] = do when bqual . tassert (AST.syntacticValue ei) $ [msg| In an additive-record expression, all fields must be syntactic values:
field:
$1
expression:
$5:ei
|] (AST.uidToLid ui) (mσi, mσRow'') ← splitRow mσRow' ui (ei', σi) ← infer request δ γ ei mσi tell ([[fdQ| $uid:ui = $ei' |]], Endo (TyRow ui σi)) return mσRow'' eachFld _ [fdQ| $antiF:a |] = $(AST.antifail) (mσ2, (flds', σs)) ← runWriterT (foldM eachFld mσRow flds) (e2', σ2) ← infer request δ γ e2 (tyBinOp tcRecord qual <$> mσ2) σRow ← newTVTy σ2 <: tyBinOp tcRecord qual σRow σ' ← maybeGen e0 φ γ (tyBinOp tcRecord qual (appEndo σs σRow)) return (flds', e2', σ') -- | Infer the type of an n-ary application expression inferApp ∷ MonadConstraint tv r m ⇒ Δ tv → Γ tv → Type tv → [AST.Expr R] → m ([AST.Expr R], Type tv) inferApp δ γ ρ e1n = do traceN 2 (TraceIn ("inferApp", ρ, e1n)) (σ1m, σ) ← funmatchN (length e1n) ρ refs ← replicateM (length σ1m) (newRef Nothing) withPinnedTVs ρ $ subsumeN [ (σi, do (ei', σi') ← infer (request Exists) δ γ ei (Just σi) writeRef refi (Just ei') traceN 2 ("subsumeI", i, ei, σi', σi) if AST.isAnnotated ei then σi' <: σi else σi' ≤ σi) | i ← [ 0 ∷ Int .. ] | refi ← refs | σi ← σ1m | ei ← e1n ] e1m' ← for refs $ readRef >=> maybe (typeBug "inferApp" "ref contained Nothing") return if length σ1m < length e1n then do ρ' ← instantiate σ first (e1m' ++) <$> inferApp δ γ ρ' (drop (length σ1m) e1n) else do traceN 2 (TraceOut ("inferApp", σ)) return (e1m', σ) -- | Type check a list of pattern match alternatives tcMatchCases ∷ MonadConstraint tv r m ⇒ Request tv → Δ tv → Γ tv → Type tv → [AST.CaseAlt R] → Maybe (Type tv) → m ([AST.CaseAlt R], Type tv) tcMatchCases _ _ _ _ [] _ = ([],) <$> newTVTy tcMatchCases φ δ γ σ ([caQ| #$uid:n $opt:mπi → $ei |]:cas) mσ = do traceN 3 ("tcMatchCases", φ, σ, "variant", n, mπi, ei) β ← newTVTy σ1 ← newTVTy σ2 ← newTVTy σ ≤≥ TyApp tcVariant [TyRow n σ1 σ2] (γ', αs) ← case mπi of Just πi → do ((_, σs), αs) ← collectTVs (tcPatt δ γ πi (Just σ1) ei) γ' ← γ !+! πi -:*- σs return (γ', αs) Nothing → do σ1 =: tyUnit return (γ, []) (ei', σi) ← infer (request φ γ αs) δ γ' ei mσ (cas', σk) ← if null cas then do σ2 ≤≥ tyNulOp tcRowEnd return ([], β) else tcMatchCases φ δ γ (TyApp tcVariant [σ2]) cas mσ if AST.isAnnotated ei then σi <: β else σi ≤ β σk <: β return ([caQ|+ `$uid:n $opt:mπi → $ei' |]:cas', β) -- Should we do this case automatically like this?: {- tcMatchCases φ δ γ σ ([caQ| `$uid:n $opt:mπi → $ei |]:cas) mσ | maybe True (isPattTotal γ) mπi = do tcMatchCases φ δ γ σ ([caQ| #$uid:n $opt:mπi → $ei |]:cas) mσ -} tcMatchCases φ δ γ σ ([caQ| $πi → $ei |]:cas) mσ = do traceN 3 ("tcMatchCases", φ, σ, πi, ei) β ← newTVTy ((_, σs), αs) ← collectTVs (tcPatt δ γ πi (Just σ) ei) γ' ← γ !+! πi -:*- σs (ei', σi) ← infer (request φ γ αs) δ γ' ei mσ (cas', σk) ← tcMatchCases φ δ γ σ cas mσ if AST.isAnnotated ei then σi <: β else σi ≤ β σk <: β return ([caQ|+ $πi → $ei' |]:cas', β) tcMatchCases _ _ _ _ ([caQ| $antiC:a |]:_) _ = $(AST.antifail) tcLetRecBindings ∷ MonadConstraint tv r m ⇒ Γ tv → [AST.Binding R] → m ([AST.Binding R], [VarId], [Type tv]) tcLetRecBindings γ bs = withTVsOf mempty γ bs $ \δ → tcLetRecBindingsΔ δ γ bs tcLetRecBindingsΔ ∷ MonadConstraint tv r m ⇒ Δ tv → Γ tv → [AST.Binding R] → m ([AST.Binding R], [VarId], [Type tv]) tcLetRecBindingsΔ δ γ bs = do (ns, es) ← unzip <$> mapM unBinding bs let mannots = AST.getExprAnnot <$> es σs ← mapM (maybe newTVTy (tcType δ γ)) mannots γ' ← γ !+! ns -:*- σs (es', σs') ← unzip <$> sequence [ do tassert (AST.syntacticValue ei) [msg| In let rec, binding for $q:ni is not a syntactic value. |] σi ⊏: Qu infer request δ γ' ei (σi <$ mannoti) | ni ← ns | ei ← es | mannoti ← mannots | σi ← σs ] zipWithM (<:) σs' σs σs'' ← generalizeList True (rankΓ γ) σs' return (zipWith AST.bnBind ns es', ns, σs'') where unBinding [bnQ| $vid:x = $e |] = return (x, e) unBinding [bnQ| $antiB:a |] = $(AST.antifail) --- --- MISCELLANEOUS HELPERS --- -- | Determine which quantifiers may appear at the beginning of -- a type scheme, given a list of quantifiers that may be presumed -- to belong to an unsubstituted variable. prenexFlavors ∷ Type tv → Request tv' → Request tv' prenexFlavors σ φ = case σ of TyQu Exists _ (TyQu Forall _ _) → φ { rqEx = True, rqAll = True } TyQu Exists _ (TyVar _) → φ { rqEx = True } TyQu Exists _ _ → φ { rqEx = True, rqAll = False } TyQu Forall _ _ → φ { rqEx = False, rqAll = True } TyVar _ → φ _ → φ { rqEx = False, rqAll = False } -- | To compute the qualifier expression for a function type. arrowQualifier ∷ Ord tv ⇒ Γ tv → AST.Expr R → QExpV tv arrowQualifier γ e = bigJoin [ qualifier (γ =..= n) | n ← M.keys (AST.fv e) ] --- --- SUBSUMPTION OPERATIONS --- -- | Given a function arity and a type, extracts a list of argument -- types and a result type of at most the given arity. funmatchN ∷ MonadConstraint tv r m ⇒ Int → Type tv → m ([Type tv], Type tv) funmatchN n0 σ0 = loop False n0 =<< subst σ0 where loop _ 0 σ = return ([], σ) loop okay n σ = case σ of TyApp tc [σ1, _, σ'] | tc == tcFun → first (σ1:) <$> loop True (n - 1) σ' TyApp _ _ | Next σ' ← headReduceType σ → loop okay n σ' TyVar (Free α) | tvFlavorIs Universal α → do β1 ← newTVTy qe ← qvarexp . Free <$> newTV' KdQual β2 ← newTVTy σ =: tyFun β1 qe β2 return ([β1], β2) TyMu _ σ1 → loop okay n (openTy 0 [σ] σ1) _ | okay → return ([], σ) | otherwise → do tErrExp_ [msg| In application expression, operator is not a function: |] [msg| $σ |] [msg| a function type |] βs ← replicateM n newTVTy β2 ← newTVTy return (βs, β2) --- --- Testing test_tcExpr ∷ AST.Expr R → IO (Either [AlmsError] (Type (TV IORef), ConstraintState (TV IORef) IORef)) test_tcExpr e = runConstraintIO constraintState0 (subst =<< snd <$> tcExpr test_g0 e)